mirror of https://github.com/apache/cassandra
Accord Executor QoS
Fairness: tasks are grouped by kind of work, and for each group we track work arrival and service via decaying counters. If work for some group(s) begin to be served at a much lower rate than some other group (e.g. because that group has a large backlog, that by simple priority comes first), then we begin processing some fraction of the work by service-ratio rather than priority. This means that we cannot starve request processing because, e.g., a sync point has produced thousands of state save tasks. Incremental processing: tasks that process many keys may be declared INCR or ASYNC. ASYNC indicates the keys are needed (so should be loaded), but may be used in follow-up work so the task may be executed if the data is not yet loaded. An ASYNC task may be followed by an INCR task that processes keys as they are loaded, with limits to the batch sizes. This limits the latency impact of processing larger transactions such as sync points. INCR tasks may be declared to be processed in either an arbitrary order with no isolation, or “atomically” with its parent task. In the latter case, the complete unit of work appears to execute as a single execution to external observers (blocking progress on unprocessed keys). Key-level ordering: AccordCacheEntry can inflate a special Queue object that can be used to impose ordering constraints: FIFO for atomic work (once it has been part processed, or submitted as part of a parent task); prioritised for tasks that have a key-level priority to impose (i.e. PreAccept/Accept/Commit/Stable/Apply want to be ordered by TxnId); and unsequenced tasks for those that have no sequencing restrictions. Additional improvements: - AccordCacheEntry are now explicitly LOCKED for the duration of their usage, which may span multiple executions for long running INCR tasks. - ExclusiveExecutor releases its ownership lock immediately, since AccordCacheEntry locks guarantee safety (as they will not be released until the task is cleaned up) - CassandraThread tracks active and locked AccordExecutor, so that we may safely and more aggressively use executeMaybeImmediately, safe in the knowledge it will never permit cross-executor executions or multiple executor locks to be held. Also: - Rename PreLoadContext -> ExecutionContext - Break out AccordExecutor/AccordTask into separate package
This commit is contained in:
parent
1cffe9a8a0
commit
37b4fbf08f
|
|
@ -1 +1 @@
|
|||
Subproject commit fff32de2e915772fbc70d16b1c32346313877838
|
||||
Subproject commit 84ac4db03251c3b842c98a29868e072792662f51
|
||||
|
|
@ -31,6 +31,7 @@ public class CassandraThread extends FastThreadLocalThread implements AccordExec
|
|||
private ExecutorLocals executorLocals;
|
||||
private AccordExecutor accordActiveExecutor;
|
||||
private AccordExecutor accordLockedExecutor;
|
||||
private int accordLockedExecutorDepth;
|
||||
private volatile AccordExecutor.Task accordActiveTask;
|
||||
private static final AtomicReferenceFieldUpdater<CassandraThread, AccordExecutor.Task> accordActiveTaskUpdater = AtomicReferenceFieldUpdater.newUpdater(CassandraThread.class, AccordExecutor.Task.class, "accordActiveTask");
|
||||
|
||||
|
|
@ -113,18 +114,19 @@ public class CassandraThread extends FastThreadLocalThread implements AccordExec
|
|||
}
|
||||
|
||||
@Override
|
||||
public final boolean trySetAccordLockedExecutor(AccordExecutor newLockedExecutor)
|
||||
public final boolean tryEnterAccordLockedExecutor(AccordExecutor newLockedExecutor)
|
||||
{
|
||||
if (accordLockedExecutor != null)
|
||||
return false;
|
||||
accordLockedExecutor = newLockedExecutor;
|
||||
if (accordLockedExecutor == null) accordLockedExecutor = newLockedExecutor;
|
||||
else if (accordLockedExecutor != newLockedExecutor) return false;
|
||||
++accordLockedExecutorDepth;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void clearAccordLockedExecutor()
|
||||
public final void exitAccordLockedExecutor()
|
||||
{
|
||||
accordLockedExecutor = null;
|
||||
if (--accordLockedExecutorDepth == 0)
|
||||
accordLockedExecutor = null;
|
||||
}
|
||||
|
||||
public final AccordExecutor.Task accordActiveTask()
|
||||
|
|
|
|||
|
|
@ -156,31 +156,12 @@ public class AccordConfig
|
|||
*/
|
||||
PHASE_ONLY,
|
||||
|
||||
/**
|
||||
* Always pick the task by priority.
|
||||
*/
|
||||
PRIORITY_BUDGET,
|
||||
|
||||
/**
|
||||
* Pick by phase first, so the highest phase with budget ALWAYS runs.
|
||||
* Within a phase, pick by priority.
|
||||
*/
|
||||
PHASE_BUDGET,
|
||||
|
||||
/**
|
||||
* Pick by phase first, selecting the phase that has processed the least work recently relative to arrivals.
|
||||
* Within a phase, pick by priority.
|
||||
*/
|
||||
PHASE_FAIR,
|
||||
|
||||
/**
|
||||
* Pick by phase first, selecting the phase that has processed the least work recently relative to arrivals.
|
||||
* However, each phase has a budget that is consumed on dispatch, and we pick only from those phases with budget.
|
||||
* If there is no phase with budget, the budget resets.
|
||||
* Within a phase, pick by priority.
|
||||
*/
|
||||
PHASE_BUDGET_FAIR,
|
||||
|
||||
/**
|
||||
* While phases are within a threshold of imbalance, pick tasks by priority.
|
||||
* Once the threshold is crossed, over-processed phases have a small penalty applied
|
||||
|
|
@ -190,8 +171,6 @@ public class AccordConfig
|
|||
* Within a phase, pick by priority.
|
||||
*/
|
||||
BLENDED_PRIORITY_PHASE_FAIR,
|
||||
|
||||
BLENDED_PRIORITY_PHASE_BUDGET_FAIR,
|
||||
}
|
||||
|
||||
public QueueShardModel queue_shard_model = THREAD_POOL_PER_SHARD;
|
||||
|
|
@ -212,8 +191,29 @@ public class AccordConfig
|
|||
|
||||
public Integer queue_flow_imbalance_onset = null;
|
||||
public Integer queue_flow_imbalance_width_shift = null;
|
||||
|
||||
public String queue_active_limits;
|
||||
public String queue_budgets;
|
||||
|
||||
public Boolean queue_nonsync_enabled;
|
||||
|
||||
/**
|
||||
* Size at which we will begin processing a task that is ASYNC, INCR OR INCR_ATOMIC.
|
||||
* Note that a size of zero will effectively give implicit priority to INCR_ATOMIC tasks, as they may immediately
|
||||
* take a FIFO queue slot (which is processed preferentially).
|
||||
*/
|
||||
public Integer queue_nonsync_min_batch_size;
|
||||
|
||||
/**
|
||||
* If there are more than min_batch_size keys ready for an ASYNC, INCR or INCR_ATOMIC task,
|
||||
* process up to this many keys at once.
|
||||
*/
|
||||
public Integer queue_nonsync_max_batch_size;
|
||||
|
||||
/**
|
||||
* An ASYNC, INCR or INCR_ATOMIC task that is ready to run but waiting for batch_size work will proceed
|
||||
* once this number of tasks are blocked behind it, regardless of batch_size.
|
||||
*/
|
||||
public Integer queue_nonsync_blocked_limit;
|
||||
|
||||
/**
|
||||
* If set, the signal loop does not match park/unpark pairs, but instead consumers perform timed-park spin waits
|
||||
|
|
@ -240,9 +240,6 @@ public class AccordConfig
|
|||
*/
|
||||
public volatile OptionaldPositiveInt command_store_shard_count = OptionaldPositiveInt.UNDEFINED;
|
||||
|
||||
public volatile OptionaldPositiveInt max_queued_loads = OptionaldPositiveInt.UNDEFINED;
|
||||
public volatile OptionaldPositiveInt max_queued_range_loads = OptionaldPositiveInt.UNDEFINED;
|
||||
|
||||
public volatile OptionaldPositiveInt progress_log_concurrency = OptionaldPositiveInt.UNDEFINED;
|
||||
public DurationSpec.IntMillisecondsBound progress_log_query_fallback_timeout = new DurationSpec.IntMillisecondsBound("1m");
|
||||
|
||||
|
|
|
|||
|
|
@ -1650,7 +1650,7 @@ public class AccordDebugKeyspace extends VirtualKeyspace
|
|||
{
|
||||
try (AccordCommandStore.ExclusiveCaches caches = commandStore.lockCaches())
|
||||
{
|
||||
AccordCacheEntry<TxnId, Command> entry = caches.commands().getUnsafe(txnId);
|
||||
AccordCacheEntry<TxnId, Command, ?> entry = caches.commands().getUnsafe(txnId);
|
||||
return entry == null ? null : entry.getExclusive();
|
||||
}
|
||||
}
|
||||
|
|
@ -1895,7 +1895,7 @@ public class AccordDebugKeyspace extends VirtualKeyspace
|
|||
AccordService.getBlocking(accord.node()
|
||||
.commandStores()
|
||||
.forId(commandStoreId)
|
||||
.chain(ExecutionContext.contextFor(txnId, TXN_OPS), apply)
|
||||
.chain(ExecutionContext.unsequenced(txnId, TXN_OPS), apply)
|
||||
.flatMap(i -> i));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ import org.slf4j.LoggerFactory;
|
|||
|
||||
import accord.api.RoutingKey;
|
||||
import accord.local.Command;
|
||||
import accord.local.SafeState;
|
||||
import accord.local.cfk.CommandsForKey;
|
||||
import accord.local.cfk.Serialize;
|
||||
import accord.primitives.Routable;
|
||||
|
|
@ -66,6 +67,7 @@ import org.apache.cassandra.metrics.LogLinearHistogram;
|
|||
import org.apache.cassandra.metrics.ShardedHitRate;
|
||||
import org.apache.cassandra.service.accord.AccordCache.Adapter.Shrink;
|
||||
import org.apache.cassandra.service.accord.AccordCacheEntry.LoadExecutor;
|
||||
import org.apache.cassandra.service.accord.AccordCacheEntry.Loading;
|
||||
import org.apache.cassandra.service.accord.AccordCacheEntry.Status;
|
||||
import org.apache.cassandra.service.accord.AccordSafeCommandsForKey.CommandsForKeyCacheEntry;
|
||||
import org.apache.cassandra.service.accord.events.CacheEvents;
|
||||
|
|
@ -79,6 +81,8 @@ import org.apache.cassandra.utils.ObjectSizes;
|
|||
|
||||
import static accord.utils.Invariants.illegalState;
|
||||
import static accord.utils.Invariants.require;
|
||||
import static org.apache.cassandra.service.accord.AccordCacheEntry.AGE_MASK;
|
||||
import static org.apache.cassandra.service.accord.AccordCacheEntry.GENERATION_MASK;
|
||||
import static org.apache.cassandra.service.accord.AccordCacheEntry.Status.EVICTED;
|
||||
import static org.apache.cassandra.service.accord.AccordCacheEntry.Status.LOADED;
|
||||
import static org.apache.cassandra.service.accord.AccordCacheEntry.Status.MODIFIED;
|
||||
|
|
@ -106,7 +110,7 @@ public class AccordCache implements CacheSize
|
|||
VALIDATE_LOAD_ON_EVICT = value;
|
||||
}
|
||||
|
||||
public interface Adapter<K, V, S>
|
||||
public interface Adapter<K, V, S extends SafeState<V> & AccordSafeState<K, V, S>>
|
||||
{
|
||||
enum Shrink { EVICT, DONE, PERFORM_WITHOUT_LOCK }
|
||||
|
||||
|
|
@ -122,10 +126,10 @@ public class AccordCache implements CacheSize
|
|||
long estimateHeapSize(V value);
|
||||
long estimateShrunkHeapSize(Object shrunk);
|
||||
boolean validate(AccordCommandStore commandStore, K key, V value);
|
||||
S safeRef(AccordCacheEntry<K, V> node);
|
||||
S safeRef(AccordCacheEntry<K, V, S> node);
|
||||
default Comparator<K> keyComparator() { return null; }
|
||||
|
||||
default AccordCacheEntry<K, V> newEntry(K key, AccordCache.Type<K, V, ?>.Instance owner)
|
||||
default AccordCacheEntry<K, V, S> newEntry(K key, AccordCache.Type<K, V, S>.Instance owner)
|
||||
{
|
||||
return AccordCacheEntry.createReadyToLoad(key, owner);
|
||||
}
|
||||
|
|
@ -151,8 +155,8 @@ public class AccordCache implements CacheSize
|
|||
|
||||
private final List<Type<?, ?, ?>> types = new CopyOnWriteArrayList<>();
|
||||
final AccordCacheEntry.SaveExecutor saveExecutor;
|
||||
private final IntrusiveLinkedList<AccordCacheEntry<?,?>> evictQueue = new IntrusiveLinkedList<>();
|
||||
private final IntrusiveLinkedList<AccordCacheEntry<?,?>> noEvictQueue = new IntrusiveLinkedList<>();
|
||||
private final IntrusiveLinkedList<AccordCacheEntry<?,?, ?>> evictQueue = new IntrusiveLinkedList<>();
|
||||
private final IntrusiveLinkedList<AccordCacheEntry<?,?, ?>> noEvictQueue = new IntrusiveLinkedList<>();
|
||||
|
||||
private long unreferencedBytes;
|
||||
private int unreferenced;
|
||||
|
|
@ -192,16 +196,16 @@ public class AccordCache implements CacheSize
|
|||
*/
|
||||
void processNoEvictQueue()
|
||||
{
|
||||
noEvictGeneration = (noEvictGeneration + 1) & 0xffff;
|
||||
noEvictGeneration = (noEvictGeneration + 1) & GENERATION_MASK;
|
||||
if (noEvictQueue.isEmpty())
|
||||
return;
|
||||
|
||||
Iterator<AccordCacheEntry<?, ?>> iter = noEvictQueue.iterator();
|
||||
Iterator<AccordCacheEntry<?, ?, ?>> iter = noEvictQueue.iterator();
|
||||
int skipCount = 3;
|
||||
while (skipCount > 0 && iter.hasNext())
|
||||
{
|
||||
AccordCacheEntry<?, ?> entry = iter.next();
|
||||
int age = (noEvictGeneration - entry.noEvictGeneration()) & 0xffff;
|
||||
AccordCacheEntry<?, ?, ?> entry = iter.next();
|
||||
int age = (noEvictGeneration - entry.noEvictGeneration()) & GENERATION_MASK;
|
||||
if (age >= entry.noEvictMaxAge())
|
||||
{
|
||||
evictNoEvict.warn(entry, age, entry.noEvictMaxAge());
|
||||
|
|
@ -226,14 +230,14 @@ public class AccordCache implements CacheSize
|
|||
|
||||
while (bytesCached > maxSizeInBytes && !evictQueue.isEmpty())
|
||||
{
|
||||
AccordCacheEntry<?, ?> node = evictQueue.peek();
|
||||
AccordCacheEntry<?, ?, ?> node = evictQueue.peek();
|
||||
shrinkOrEvict(lock, node);
|
||||
}
|
||||
tryShrinkOrEvict = false;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
private <K, V> void shrinkOrEvict(Lock lock, AccordCacheEntry<K, V> node)
|
||||
private <K, V> void shrinkOrEvict(Lock lock, AccordCacheEntry<K, V, ?> node)
|
||||
{
|
||||
require(node.references() == 0);
|
||||
|
||||
|
|
@ -244,7 +248,7 @@ public class AccordCache implements CacheSize
|
|||
}
|
||||
else
|
||||
{
|
||||
IntrusiveLinkedList<AccordCacheEntry<?,?>> queue;
|
||||
IntrusiveLinkedList<AccordCacheEntry<?,?, ?>> queue;
|
||||
queue = node.isNoEvict() ? noEvictQueue : evictQueue;
|
||||
node.unlink();
|
||||
if (shrink == Shrink.DONE)
|
||||
|
|
@ -272,7 +276,7 @@ public class AccordCache implements CacheSize
|
|||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public <K, V> void tryEvict(AccordCacheEntry<K, V> node)
|
||||
public <K, V> void tryEvict(AccordCacheEntry<K, V, ?> node)
|
||||
{
|
||||
require(node.references() == 0);
|
||||
|
||||
|
|
@ -290,7 +294,6 @@ public class AccordCache implements CacheSize
|
|||
case LOADING:
|
||||
node.loading().loading.cancel();
|
||||
case WAITING_TO_LOAD:
|
||||
Invariants.paranoid(node.loadingOrWaiting().waiters == null);
|
||||
case LOADED:
|
||||
node.unlink();
|
||||
evict(node, true);
|
||||
|
|
@ -305,7 +308,7 @@ public class AccordCache implements CacheSize
|
|||
}
|
||||
}
|
||||
|
||||
public void saveWhenReadyExclusive(AccordCacheEntry<?, ?> entry, Runnable onSuccess)
|
||||
public void saveWhenReadyExclusive(AccordCacheEntry<?, ?, ?> entry, Runnable onSuccess)
|
||||
{
|
||||
if (!entry.isSavingOrWaiting() && !entry.saveWhenReady())
|
||||
onSuccess.run();
|
||||
|
|
@ -313,7 +316,7 @@ public class AccordCache implements CacheSize
|
|||
entry.savingOrWaitingToSave().identity.onSuccess(onSuccess);
|
||||
}
|
||||
|
||||
private <K> void evict(AccordCacheEntry<K, ?> node, boolean updateUnreferenced)
|
||||
private <K> void evict(AccordCacheEntry<K, ?, ?> node, boolean updateUnreferenced)
|
||||
{
|
||||
if (logger.isTraceEnabled())
|
||||
logger.trace("Evicting {}", node);
|
||||
|
|
@ -336,25 +339,25 @@ public class AccordCache implements CacheSize
|
|||
if (node.status() == LOADED && VALIDATE_LOAD_ON_EVICT)
|
||||
owner.validateLoadEvicted(node);
|
||||
|
||||
AccordCacheEntry<K, ?> self = node.owner.remove(node.key());
|
||||
AccordCacheEntry<K, ?, ?> self = node.owner.remove(node.key());
|
||||
Invariants.require(self.references() == 0);
|
||||
require(self == node, "Leaked node detected; was attempting to remove %s but cache had %s", node, self);
|
||||
node.notifyListeners(Listener::onEvict);
|
||||
node.evicted();
|
||||
}
|
||||
|
||||
<P1, P2, K, V> Collection<AccordTask<?>> load(LoadExecutor<P1, P2> loadExecutor, P1 p1, P2 p2, AccordCacheEntry<K, V> node)
|
||||
<P1, P2, K, V> Loading load(LoadExecutor<P1, P2> loadExecutor, P1 p1, P2 p2, AccordCacheEntry<K, V, ?> node)
|
||||
{
|
||||
return node.load(loadExecutor, p1, p2).waiters();
|
||||
return node.load(loadExecutor, p1, p2);
|
||||
}
|
||||
|
||||
<K, V> void loaded(AccordCacheEntry<K, V> node, V value)
|
||||
<K, V> void loaded(AccordCacheEntry<K, V, ?> node, V value)
|
||||
{
|
||||
node.loaded(value);
|
||||
node.notifyListeners(Listener::onUpdate);
|
||||
}
|
||||
|
||||
<K, V> void failedToLoad(AccordCacheEntry<K, V> node)
|
||||
<K, V> void failedToLoad(AccordCacheEntry<K, V, ?> node)
|
||||
{
|
||||
Invariants.require(node.references() == 0);
|
||||
if (node.isUnqueued())
|
||||
|
|
@ -367,38 +370,38 @@ public class AccordCache implements CacheSize
|
|||
evict(node, true);
|
||||
}
|
||||
|
||||
<K, V> void saved(AccordCacheEntry<K, V> node, Object identity, Throwable fail)
|
||||
<K, V> void saved(AccordCacheEntry<K, V, ?> node, Object identity, Throwable fail)
|
||||
{
|
||||
if (node.saved(identity, fail) && node.references() == 0 && node.isUnqueued())
|
||||
evictQueue.addFirst(node); // add to front since we have just saved, so we were eligible for eviction
|
||||
}
|
||||
|
||||
public <K, V, S extends AccordSafeState<K, V>> void release(S safeRef, AccordTask<?> owner)
|
||||
public <K, V, S extends SafeState<V> & AccordSafeState<K, V, S>> void release(S safeRef, AccordTask<?> owner)
|
||||
{
|
||||
safeRef.global().owner.release(safeRef, owner);
|
||||
}
|
||||
|
||||
public <K, V, S extends AccordSafeState<K, V>> Type<K, V, S> newType(Class<K> keyClass, Adapter<K, V, S> adapter, AccordCacheMetrics.Shard metrics)
|
||||
public <K, V, S extends SafeState<V> & AccordSafeState<K, V, S>> Type<K, V, S> newType(Class<K> keyClass, Adapter<K, V, S> adapter, AccordCacheMetrics.Shard metrics)
|
||||
{
|
||||
Type<K, V, S> instance = new Type<>(keyClass, adapter, metrics);
|
||||
types.add(instance);
|
||||
return instance;
|
||||
}
|
||||
|
||||
public <K, V, S extends AccordSafeState<K, V>> Type<K, V, S> newType(
|
||||
public <K, V, S extends SafeState<V> & AccordSafeState<K, V, S>> Type<K, V, S> newType(
|
||||
Class<K> keyClass,
|
||||
BiFunction<AccordCommandStore, K, V> loadFunction,
|
||||
QuadFunction<AccordCommandStore, K, V, Object, Runnable> saveFunction,
|
||||
Function<V, V> quickShrink,
|
||||
TriFunction<AccordCommandStore, K, V, Boolean> validateFunction,
|
||||
ToLongFunction<V> heapEstimator,
|
||||
Function<AccordCacheEntry<K, V>, S> safeRefFactory,
|
||||
Function<AccordCacheEntry<K, V, S>, S> safeRefFactory,
|
||||
AccordCacheMetrics.Shard metrics)
|
||||
{
|
||||
return newType(keyClass, loadFunction, saveFunction, quickShrink, (i, j) -> j, (c, i, j) -> (V)j, validateFunction, heapEstimator, i -> 0, safeRefFactory, metrics);
|
||||
}
|
||||
|
||||
public <K, V, S extends AccordSafeState<K, V>> Type<K, V, S> newType(
|
||||
public <K, V, S extends SafeState<V> & AccordSafeState<K, V, S>> Type<K, V, S> newType(
|
||||
Class<K> keyClass,
|
||||
BiFunction<AccordCommandStore, K, V> loadFunction,
|
||||
QuadFunction<AccordCommandStore, K, V, Object, Runnable> saveFunction,
|
||||
|
|
@ -408,7 +411,7 @@ public class AccordCache implements CacheSize
|
|||
TriFunction<AccordCommandStore, K, V, Boolean> validateFunction,
|
||||
ToLongFunction<V> heapEstimator,
|
||||
ToLongFunction<Object> shrunkHeapEstimator,
|
||||
Function<AccordCacheEntry<K, V>, S> safeRefFactory,
|
||||
Function<AccordCacheEntry<K, V, S>, S> safeRefFactory,
|
||||
AccordCacheMetrics.Shard metrics)
|
||||
{
|
||||
return newType(keyClass, new FunctionalAdapter<>(loadFunction, saveFunction, quickShrink,
|
||||
|
|
@ -425,18 +428,18 @@ public class AccordCache implements CacheSize
|
|||
|
||||
public interface Listener<K, V>
|
||||
{
|
||||
default void onAdd(AccordCacheEntry<K, V> state) {}
|
||||
default void onUpdate(AccordCacheEntry<K, V> state) {}
|
||||
default void onEvict(AccordCacheEntry<K, V> state) {}
|
||||
default void onAdd(AccordCacheEntry<K, V, ?> state) {}
|
||||
default void onUpdate(AccordCacheEntry<K, V, ?> state) {}
|
||||
default void onEvict(AccordCacheEntry<K, V, ?> state) {}
|
||||
}
|
||||
|
||||
public class Type<K, V, S extends AccordSafeState<K, V>> implements CacheSize
|
||||
public class Type<K, V, S extends SafeState<V> & AccordSafeState<K, V, S>> implements CacheSize
|
||||
{
|
||||
public class Instance implements Iterable<AccordCacheEntry<K, V>>
|
||||
public class Instance implements Iterable<AccordCacheEntry<K, V, S>>
|
||||
{
|
||||
final AccordCommandStore commandStore;
|
||||
// TODO (desired): don't need to store key separately as stored in node; ideally use a hash set that allows us to get the current entry
|
||||
private final Map<K, AccordCacheEntry<K, V>> cache = new Object2ObjectHashMap<>();
|
||||
private final Map<K, AccordCacheEntry<K, V, S>> cache = new Object2ObjectHashMap<>();
|
||||
private List<Listener<K, V>> listeners = null;
|
||||
// TODO (expected): update this after releasing the lock
|
||||
private OrderedKeys<K> orderedKeys;
|
||||
|
|
@ -446,50 +449,51 @@ public class AccordCache implements CacheSize
|
|||
this.commandStore = commandStore;
|
||||
}
|
||||
|
||||
public S acquire(K key)
|
||||
public final S acquire(K key)
|
||||
{
|
||||
AccordCacheEntry<K, V> node = acquire(key, false);
|
||||
AccordCacheEntry<K, V, S> node = acquire(key, false);
|
||||
return adapter.safeRef(node);
|
||||
}
|
||||
|
||||
public S acquireIfLoaded(K key)
|
||||
public final S acquireIfLoadedAndPermitted(K key)
|
||||
{
|
||||
AccordCacheEntry<K, V> node = acquire(key, true);
|
||||
AccordCacheEntry<K, V, S> node = acquire(key, true);
|
||||
if (node == null)
|
||||
return null;
|
||||
return adapter.safeRef(node);
|
||||
}
|
||||
|
||||
public S acquire(AccordCacheEntry<K, V> node)
|
||||
public final S acquire(AccordCacheEntry<K, V, S> node)
|
||||
{
|
||||
Invariants.require(node.owner == this);
|
||||
acquireExisting(node, false);
|
||||
return adapter.safeRef(node);
|
||||
}
|
||||
|
||||
public void recordPreAcquired(AccordSafeState<K, V> ref)
|
||||
public final void recordPreAcquired(AccordCacheEntry<K, V, S> entry)
|
||||
{
|
||||
Invariants.require(ref.global().owner == this);
|
||||
incrementCacheHits();
|
||||
Invariants.require(entry.owner == this);
|
||||
if (entry.isLoaded()) incrementCacheHits();
|
||||
else incrementCacheMisses();
|
||||
}
|
||||
|
||||
private AccordCacheEntry<K, V> acquire(K key, boolean onlyIfLoaded)
|
||||
private AccordCacheEntry<K, V, S> acquire(K key, boolean onlyIfLoadedAndPermitted)
|
||||
{
|
||||
AccordCacheEntry<K, V> node = cache.get(key);
|
||||
AccordCacheEntry<K, V, S> node = cache.get(key);
|
||||
return node == null
|
||||
? acquireAbsent(key, onlyIfLoaded)
|
||||
: acquireExisting(node, onlyIfLoaded);
|
||||
? acquireAbsent(key, onlyIfLoadedAndPermitted)
|
||||
: acquireExisting(node, onlyIfLoadedAndPermitted);
|
||||
}
|
||||
|
||||
/*
|
||||
* Can only return a LOADING Node (or null)
|
||||
*/
|
||||
private AccordCacheEntry<K, V> acquireAbsent(K key, boolean onlyIfLoaded)
|
||||
private AccordCacheEntry<K, V, S> acquireAbsent(K key, boolean onlyIfLoaded)
|
||||
{
|
||||
incrementCacheMisses();
|
||||
if (onlyIfLoaded)
|
||||
return null;
|
||||
AccordCacheEntry<K, V> node = adapter.newEntry(key, this);
|
||||
AccordCacheEntry<K, V, S> node = adapter.newEntry(key, this);
|
||||
node.increment();
|
||||
|
||||
Object prev = cache.put(key, node);
|
||||
|
|
@ -506,7 +510,7 @@ public class AccordCache implements CacheSize
|
|||
/*
|
||||
* Can't return EVICTED or INITIALIZED
|
||||
*/
|
||||
private AccordCacheEntry<K, V> acquireExisting(AccordCacheEntry<K, V> node, boolean onlyIfLoaded)
|
||||
private AccordCacheEntry<K, V, S> acquireExisting(AccordCacheEntry<K, V, S> node, boolean onlyIfLoadedAndPermitted)
|
||||
{
|
||||
boolean isLoaded = node.isLoaded();
|
||||
if (isLoaded)
|
||||
|
|
@ -514,8 +518,11 @@ public class AccordCache implements CacheSize
|
|||
else
|
||||
incrementCacheMisses();
|
||||
|
||||
if (onlyIfLoaded && !isLoaded)
|
||||
return null;
|
||||
if (onlyIfLoadedAndPermitted)
|
||||
{
|
||||
if (!isLoaded || node.hasFifoOrLocked())
|
||||
return null;
|
||||
}
|
||||
|
||||
if (node.increment() == 1)
|
||||
{
|
||||
|
|
@ -527,21 +534,21 @@ public class AccordCache implements CacheSize
|
|||
return node;
|
||||
}
|
||||
|
||||
public void release(AccordSafeState<K, V> safeRef, AccordTask<?> owner)
|
||||
public final void release(S safeRef, AccordTask<?> owner)
|
||||
{
|
||||
K key = safeRef.global().key();
|
||||
logger.trace("Releasing resources for {}: {}", key, safeRef);
|
||||
|
||||
AccordCacheEntry<K, V> node = cache.get(key);
|
||||
AccordCacheEntry<K, V, ?> node = cache.get(key);
|
||||
|
||||
require(!safeRef.isUnsafe());
|
||||
require(!safeRef.isReleased());
|
||||
require(safeRef.global() != null, "safeRef node is null for %s", key);
|
||||
require(safeRef.global() == node, "safeRef node not in map: %s != %s", safeRef.global(), node);
|
||||
require(node.references() > 0, "references (%d) are zero for %s (%s)", node.references(), key, node);
|
||||
require(node.isUnqueued());
|
||||
|
||||
boolean evict = false;
|
||||
if (safeRef.hasUpdate())
|
||||
if (safeRef.isModified())
|
||||
{
|
||||
V update = safeRef.current();
|
||||
if (update != null)
|
||||
|
|
@ -555,15 +562,12 @@ public class AccordCache implements CacheSize
|
|||
}
|
||||
node.notifyListeners(Listener::onUpdate);
|
||||
}
|
||||
else if (node.isLoadingOrWaiting())
|
||||
{
|
||||
node.loadingOrWaiting().remove(owner);
|
||||
}
|
||||
else
|
||||
{
|
||||
evict = node.is(LOADED) && node.isNull();
|
||||
}
|
||||
safeRef.markUnsafe();
|
||||
node.remove(owner, safeRef.isSafe());
|
||||
safeRef.setReleased();
|
||||
|
||||
if (node.decrement() == 0)
|
||||
{
|
||||
|
|
@ -596,9 +600,9 @@ public class AccordCache implements CacheSize
|
|||
tryShrinkOrEvict = true;
|
||||
}
|
||||
|
||||
AccordCacheEntry<K, ?> remove(K key)
|
||||
final AccordCacheEntry<K, ?, ?> remove(K key)
|
||||
{
|
||||
AccordCacheEntry<K, ?> result = cache.remove(key);
|
||||
AccordCacheEntry<K, ?, ?> result = cache.remove(key);
|
||||
if (orderedKeys != null && result != null)
|
||||
orderedKeys.remove(key);
|
||||
return result;
|
||||
|
|
@ -609,7 +613,7 @@ public class AccordCache implements CacheSize
|
|||
return Type.this;
|
||||
}
|
||||
|
||||
public Iterable<K> keysBetween(K start, boolean startInclusive, K end, boolean endInclusive)
|
||||
public final Iterable<K> keysBetween(K start, boolean startInclusive, K end, boolean endInclusive)
|
||||
{
|
||||
if (orderedKeys == null)
|
||||
orderedKeys = new OrderedKeys<>(adapter.keyComparator(), cache.keySet());
|
||||
|
|
@ -618,15 +622,15 @@ public class AccordCache implements CacheSize
|
|||
}
|
||||
|
||||
@Override
|
||||
public Iterator<AccordCacheEntry<K, V>> iterator()
|
||||
public final Iterator<AccordCacheEntry<K, V, S>> iterator()
|
||||
{
|
||||
return cache.values().iterator();
|
||||
}
|
||||
|
||||
void validateLoadEvicted(AccordCacheEntry<?, ?> node)
|
||||
final void validateLoadEvicted(AccordCacheEntry<?, ?, ?> node)
|
||||
{
|
||||
@SuppressWarnings("unchecked")
|
||||
AccordCacheEntry<K, V> state = (AccordCacheEntry<K, V>) node;
|
||||
AccordCacheEntry<K, V, ?> state = (AccordCacheEntry<K, V, ?>) node;
|
||||
K key = state.key();
|
||||
V evicted = state.tryGetFull();
|
||||
if (evicted == null)
|
||||
|
|
@ -649,46 +653,46 @@ public class AccordCache implements CacheSize
|
|||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public AccordCacheEntry<K, V> getUnsafe(K key)
|
||||
public final AccordCacheEntry<K, V, ?> getUnsafe(K key)
|
||||
{
|
||||
return cache.get(key);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public boolean isReferenced(K key)
|
||||
public final boolean isReferenced(K key)
|
||||
{
|
||||
AccordCacheEntry<K, V> node = cache.get(key);
|
||||
AccordCacheEntry<K, V, ?> node = cache.get(key);
|
||||
return node != null && node.references() > 0;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
boolean keyIsReferenced(Object key, Class<? extends AccordSafeState<?, ?>> valClass)
|
||||
final boolean keyIsReferenced(Object key, Class<? extends AccordSafeState<?, ?, S>> valClass)
|
||||
{
|
||||
AccordCacheEntry<?, ?> node = cache.get(key);
|
||||
AccordCacheEntry<?, ?, ?> node = cache.get(key);
|
||||
return node != null && node.references() > 0;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
boolean keyIsCached(Object key, Class<? extends AccordSafeState<?, ?>> valClass)
|
||||
final boolean keyIsCached(Object key, Class<? extends AccordSafeState<?, ?, S>> valClass)
|
||||
{
|
||||
AccordCacheEntry<?, ?> node = cache.get(key);
|
||||
AccordCacheEntry<?, ?, ?> node = cache.get(key);
|
||||
return node != null;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
int references(Object key, Class<? extends AccordSafeState<?, ?>> valClass)
|
||||
final int references(Object key, Class<? extends AccordSafeState<?, ?, S>> valClass)
|
||||
{
|
||||
AccordCacheEntry<?, ?> node = cache.get(key);
|
||||
AccordCacheEntry<?, ?, ?> node = cache.get(key);
|
||||
return node != null ? node.references() : 0;
|
||||
}
|
||||
|
||||
void notifyListeners(BiConsumer<Listener<K, V>, AccordCacheEntry<K, V>> notify, AccordCacheEntry<K, V> node)
|
||||
final void notifyListeners(BiConsumer<Listener<K, V>, AccordCacheEntry<K, V, ?>> notify, AccordCacheEntry<K, V, ?> node)
|
||||
{
|
||||
notifyListeners(listeners, notify, node);
|
||||
notifyListeners(typeListeners, notify, node);
|
||||
}
|
||||
|
||||
void notifyListeners(List<Listener<K, V>> listeners, BiConsumer<Listener<K, V>, AccordCacheEntry<K, V>> notify, AccordCacheEntry<K, V> node)
|
||||
final void notifyListeners(List<Listener<K, V>> listeners, BiConsumer<Listener<K, V>, AccordCacheEntry<K, V, ?>> notify, AccordCacheEntry<K, V, ?> node)
|
||||
{
|
||||
if (listeners != null)
|
||||
{
|
||||
|
|
@ -698,20 +702,20 @@ public class AccordCache implements CacheSize
|
|||
}
|
||||
}
|
||||
|
||||
public void register(Listener<K, V> l)
|
||||
public final void register(Listener<K, V> l)
|
||||
{
|
||||
if (listeners == null)
|
||||
listeners = new ArrayList<>();
|
||||
listeners.add(l);
|
||||
}
|
||||
|
||||
public void unregister(Listener<K, V> l)
|
||||
public final void unregister(Listener<K, V> l)
|
||||
{
|
||||
if (!tryUnregister(l))
|
||||
throw illegalState("Listener was not registered");
|
||||
}
|
||||
|
||||
public boolean tryUnregister(Listener<K, V> l)
|
||||
public final boolean tryUnregister(Listener<K, V> l)
|
||||
{
|
||||
if (listeners == null || !listeners.remove(l))
|
||||
return false;
|
||||
|
|
@ -719,9 +723,23 @@ public class AccordCache implements CacheSize
|
|||
listeners = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
final boolean isCommandsForKey()
|
||||
{
|
||||
return getClass() == KeyInstance.class;
|
||||
}
|
||||
}
|
||||
|
||||
private final Class<K> keyClass;
|
||||
// KeyInstance exists to provide us slightly easier discrimination about the Type an AccordCacheEntry is associated with
|
||||
public final class KeyInstance extends Instance
|
||||
{
|
||||
public KeyInstance(AccordCommandStore commandStore)
|
||||
{
|
||||
super(commandStore);
|
||||
}
|
||||
}
|
||||
|
||||
private final Class<K> keyClass; // type of key, useful primarily for toString(), but also piggyback for deciding Instance type
|
||||
private Adapter<K, V, S> adapter;
|
||||
private long bytesCached;
|
||||
private int size;
|
||||
|
|
@ -734,6 +752,8 @@ public class AccordCache implements CacheSize
|
|||
|
||||
public Type(Class<K> keyClass, Adapter<K, V, S> adapter, AccordCacheMetrics.Shard metrics)
|
||||
{
|
||||
// Integer and String permitted for testing, but the Invariant exists only to enforce that we construct the right kind of Instance
|
||||
Invariants.require(keyClass == RoutingKey.class || keyClass == TxnId.class || keyClass == String.class || keyClass == Integer.class);
|
||||
this.keyClass = keyClass;
|
||||
this.adapter = adapter;
|
||||
this.objectSize = metrics.objectSize;
|
||||
|
|
@ -751,7 +771,7 @@ public class AccordCache implements CacheSize
|
|||
// can be safely garbage collected if empty
|
||||
Instance newInstance(AccordCommandStore commandStore)
|
||||
{
|
||||
return new Instance(commandStore);
|
||||
return keyClass == RoutingKey.class ? new KeyInstance(commandStore) : new Instance(commandStore);
|
||||
}
|
||||
|
||||
private void incrementCacheHits()
|
||||
|
|
@ -867,17 +887,17 @@ public class AccordCache implements CacheSize
|
|||
}
|
||||
|
||||
@VisibleForTesting
|
||||
AccordCacheEntry<?, ?> head()
|
||||
AccordCacheEntry<?, ?, ?> head()
|
||||
{
|
||||
Iterator<AccordCacheEntry<?, ?>> iter = evictQueue.iterator();
|
||||
Iterator<AccordCacheEntry<?, ?, ?>> iter = evictQueue.iterator();
|
||||
return iter.hasNext() ? iter.next() : null;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
AccordCacheEntry<?, ?> tail()
|
||||
AccordCacheEntry<?, ?, ?> tail()
|
||||
{
|
||||
AccordCacheEntry<?,?> last = null;
|
||||
Iterator<AccordCacheEntry<?, ?>> iter = evictQueue.iterator();
|
||||
AccordCacheEntry<?,?, ?> last = null;
|
||||
Iterator<AccordCacheEntry<?, ?, ?>> iter = evictQueue.iterator();
|
||||
while (iter.hasNext())
|
||||
last = iter.next();
|
||||
return last;
|
||||
|
|
@ -888,7 +908,7 @@ public class AccordCache implements CacheSize
|
|||
return size() == 0;
|
||||
}
|
||||
|
||||
Iterable<AccordCacheEntry<?, ?>> evictionQueue()
|
||||
Iterable<AccordCacheEntry<?, ?, ?>> evictionQueue()
|
||||
{
|
||||
return evictQueue::iterator;
|
||||
}
|
||||
|
|
@ -937,10 +957,10 @@ public class AccordCache implements CacheSize
|
|||
return;
|
||||
|
||||
type.register(new AccordCache.Listener<>() {
|
||||
private final IdentityHashMap<AccordCacheEntry<?, ?>, CacheEvents.Evict> pendingEvicts = new IdentityHashMap<>();
|
||||
private final IdentityHashMap<AccordCacheEntry<?, ?, ?>, CacheEvents.Evict> pendingEvicts = new IdentityHashMap<>();
|
||||
|
||||
@Override
|
||||
public void onAdd(AccordCacheEntry<K, V> state)
|
||||
public void onAdd(AccordCacheEntry<K, V, ?> state)
|
||||
{
|
||||
CacheEvents.Add add = new CacheEvents.Add();
|
||||
CacheEvents.Evict evict = new CacheEvents.Evict();
|
||||
|
|
@ -957,7 +977,7 @@ public class AccordCache implements CacheSize
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onEvict(AccordCacheEntry<K, V> state)
|
||||
public void onEvict(AccordCacheEntry<K, V, ?> state)
|
||||
{
|
||||
CacheEvents.Evict event = pendingEvicts.remove(state);
|
||||
if (event == null) return;
|
||||
|
|
@ -967,7 +987,7 @@ public class AccordCache implements CacheSize
|
|||
});
|
||||
}
|
||||
|
||||
private static void updateMutable(AccordCache.Type<?, ?, ?> type, AccordCacheEntry<?, ?> state, CacheEvents event)
|
||||
private static void updateMutable(AccordCache.Type<?, ?, ?> type, AccordCacheEntry<?, ?, ?> state, CacheEvents event)
|
||||
{
|
||||
event.status = state.status().name();
|
||||
|
||||
|
|
@ -987,7 +1007,7 @@ public class AccordCache implements CacheSize
|
|||
event.update();
|
||||
}
|
||||
|
||||
static class FunctionalAdapter<K, V, S> implements Adapter<K, V, S>
|
||||
static class FunctionalAdapter<K, V, S extends SafeState<V> & AccordSafeState<K, V, S>> implements Adapter<K, V, S>
|
||||
{
|
||||
final BiFunction<AccordCommandStore, K, V> load;
|
||||
final QuadFunction<AccordCommandStore, K, V, Object, Runnable> save;
|
||||
|
|
@ -997,8 +1017,8 @@ public class AccordCache implements CacheSize
|
|||
final TriFunction<AccordCommandStore, K, V, Boolean> validate;
|
||||
final ToLongFunction<V> estimateHeapSize;
|
||||
final ToLongFunction<Object> estimateShrunkHeapSize;
|
||||
final Function<AccordCacheEntry<K, V>, S> newSafeRef;
|
||||
final BiFunction<K, AccordCache.Type<K, V, ?>.Instance, AccordCacheEntry<K, V>> newNode;
|
||||
final Function<AccordCacheEntry<K, V, S>, S> newSafeRef;
|
||||
final BiFunction<K, AccordCache.Type<K, V, S>.Instance, AccordCacheEntry<K, V, S>> newNode;
|
||||
|
||||
FunctionalAdapter(BiFunction<AccordCommandStore, K, V> load,
|
||||
QuadFunction<AccordCommandStore, K, V, Object, Runnable> save,
|
||||
|
|
@ -1007,8 +1027,8 @@ public class AccordCache implements CacheSize
|
|||
TriFunction<AccordCommandStore, K, V, Boolean> validate,
|
||||
ToLongFunction<V> estimateHeapSize,
|
||||
ToLongFunction<Object> estimateShrunkHeapSize,
|
||||
Function<AccordCacheEntry<K, V>, S> newSafeRef,
|
||||
BiFunction<K, Type<K, V, ?>.Instance, AccordCacheEntry<K, V>> newNode)
|
||||
Function<AccordCacheEntry<K, V, S>, S> newSafeRef,
|
||||
BiFunction<K, Type<K, V, S>.Instance, AccordCacheEntry<K, V, S>> newNode)
|
||||
{
|
||||
this.load = load;
|
||||
this.save = save;
|
||||
|
|
@ -1082,13 +1102,13 @@ public class AccordCache implements CacheSize
|
|||
}
|
||||
|
||||
@Override
|
||||
public S safeRef(AccordCacheEntry<K, V> node)
|
||||
public S safeRef(AccordCacheEntry<K, V, S> node)
|
||||
{
|
||||
return newSafeRef.apply(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccordCacheEntry<K, V> newEntry(K key, Type<K, V, ?>.Instance owner)
|
||||
public AccordCacheEntry<K, V, S> newEntry(K key, Type<K, V, S>.Instance owner)
|
||||
{
|
||||
return newNode.apply(key, owner);
|
||||
}
|
||||
|
|
@ -1100,7 +1120,7 @@ public class AccordCache implements CacheSize
|
|||
}
|
||||
}
|
||||
|
||||
static class SettableWrapper<K, V, S> extends FunctionalAdapter<K, V, S>
|
||||
static class SettableWrapper<K, V, S extends SafeState<V> & AccordSafeState<K, V, S>> extends FunctionalAdapter<K, V, S>
|
||||
{
|
||||
volatile BiFunction<AccordCommandStore, K, V> load;
|
||||
|
||||
|
|
@ -1110,9 +1130,9 @@ public class AccordCache implements CacheSize
|
|||
this.load = super.load;
|
||||
}
|
||||
|
||||
public static <K, V> Adapter<K, V, ?> loadOnly(BiFunction<AccordCommandStore, K, V> load)
|
||||
public static <K, V, S extends SafeState<V> & AccordSafeState<K, V, S>> Adapter<K, V, S> loadOnly(BiFunction<AccordCommandStore, K, V> load)
|
||||
{
|
||||
SettableWrapper<K, V, ?> result = new SettableWrapper<>(new NoOpAdapter<>());
|
||||
SettableWrapper<K, V, S> result = new SettableWrapper<>(new NoOpAdapter<K, V, S>());
|
||||
result.load = load;
|
||||
return result;
|
||||
}
|
||||
|
|
@ -1124,7 +1144,7 @@ public class AccordCache implements CacheSize
|
|||
}
|
||||
}
|
||||
|
||||
static class NoOpAdapter<K, V, S> implements Adapter<K, V, S>
|
||||
static class NoOpAdapter<K, V, S extends SafeState<V> & AccordSafeState<K, V, S>> implements Adapter<K, V, S>
|
||||
{
|
||||
@Override public V load(AccordCommandStore commandStore, K key) { return null; }
|
||||
@Override public Runnable save(AccordCommandStore commandStore, K key, @Nullable V value, @Nullable Object shrunk) { return null; }
|
||||
|
|
@ -1135,7 +1155,7 @@ public class AccordCache implements CacheSize
|
|||
@Override public long estimateHeapSize(V value) { return 0; }
|
||||
@Override public long estimateShrunkHeapSize(Object shrunk) { return 0; }
|
||||
@Override public boolean validate(AccordCommandStore commandStore, K key, V value) { return false; }
|
||||
@Override public S safeRef(AccordCacheEntry<K, V> node) { return null; }
|
||||
@Override public S safeRef(AccordCacheEntry<K, V, S> node) { return null; }
|
||||
}
|
||||
|
||||
public static class CommandsForKeyAdapter implements Adapter<RoutingKey, CommandsForKey, AccordSafeCommandsForKey>
|
||||
|
|
@ -1239,7 +1259,7 @@ public class AccordCache implements CacheSize
|
|||
}
|
||||
|
||||
@Override
|
||||
public AccordSafeCommandsForKey safeRef(AccordCacheEntry<RoutingKey, CommandsForKey> node)
|
||||
public AccordSafeCommandsForKey safeRef(AccordCacheEntry<RoutingKey, CommandsForKey, AccordSafeCommandsForKey> node)
|
||||
{
|
||||
return new AccordSafeCommandsForKey(node);
|
||||
}
|
||||
|
|
@ -1251,7 +1271,7 @@ public class AccordCache implements CacheSize
|
|||
}
|
||||
|
||||
@Override
|
||||
public AccordCacheEntry<RoutingKey, CommandsForKey> newEntry(RoutingKey key, Type<RoutingKey, CommandsForKey, ?>.Instance owner)
|
||||
public AccordCacheEntry<RoutingKey, CommandsForKey, AccordSafeCommandsForKey> newEntry(RoutingKey key, Type<RoutingKey, CommandsForKey, AccordSafeCommandsForKey>.Instance owner)
|
||||
{
|
||||
CommandsForKeyCacheEntry entry = new CommandsForKeyCacheEntry(key, owner);
|
||||
entry.readyToLoad();
|
||||
|
|
@ -1376,19 +1396,19 @@ public class AccordCache implements CacheSize
|
|||
}
|
||||
|
||||
@Override
|
||||
public AccordSafeCommand safeRef(AccordCacheEntry<TxnId, Command> node)
|
||||
public AccordSafeCommand safeRef(AccordCacheEntry<TxnId, Command, AccordSafeCommand> node)
|
||||
{
|
||||
return new AccordSafeCommand(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccordCacheEntry<TxnId, Command> newEntry(TxnId txnId, Type<TxnId, Command, ?>.Instance owner)
|
||||
public AccordCacheEntry<TxnId, Command, AccordSafeCommand> newEntry(TxnId txnId, Type<TxnId, Command, AccordSafeCommand>.Instance owner)
|
||||
{
|
||||
AccordCacheEntry<TxnId, Command> node = new AccordCacheEntry<>(txnId, owner);
|
||||
AccordCacheEntry<TxnId, Command, AccordSafeCommand> node = new AccordCacheEntry<>(txnId, owner);
|
||||
if (txnId.is(Txn.Kind.EphemeralRead))
|
||||
{
|
||||
node.initialize(null);
|
||||
int maxAge = (int)Math.min(0xff, 1 + DatabaseDescriptor.getReadRpcTimeout(TimeUnit.SECONDS));
|
||||
int maxAge = (int)Math.min(AGE_MASK, 1 + DatabaseDescriptor.getReadRpcTimeout(TimeUnit.SECONDS));
|
||||
node.markNoEvict(owner.parent().parent().noEvictGeneration, maxAge);
|
||||
}
|
||||
else
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -56,7 +56,6 @@ import accord.impl.progresslog.TxnState;
|
|||
import accord.local.Command;
|
||||
import accord.local.CommandStore;
|
||||
import accord.local.CommandStores.RangesForEpoch;
|
||||
import accord.local.CommandSummaries;
|
||||
import accord.local.ExecutionContext;
|
||||
import accord.local.ExecutionContext.Empty;
|
||||
import accord.local.MaxConflicts;
|
||||
|
|
@ -178,13 +177,16 @@ public class AccordCommandStore extends CommandStore
|
|||
@Override
|
||||
public AccordSafeCommand acquireIfLoaded(TxnId txnId)
|
||||
{
|
||||
return commands().acquireIfLoaded(txnId);
|
||||
// note: we must return false if the entry is locked to enforce ordering.
|
||||
// note importantly that this is also coupled to the safety of synchronously releasing ExclusiveExecutor.owner,
|
||||
// rather than waiting until the (potentially asynchronous) cleanup of the task completes
|
||||
return commands().acquireIfLoadedAndPermitted(txnId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccordSafeCommandsForKey acquireIfLoaded(RoutingKey key)
|
||||
{
|
||||
return commandsForKeys().acquireIfLoaded(key);
|
||||
return commandsForKeys().acquireIfLoadedAndPermitted(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -299,7 +301,7 @@ public class AccordCommandStore extends CommandStore
|
|||
void tryPreSetup(AccordTask<?> task)
|
||||
{
|
||||
if (inStore() && current != null)
|
||||
task.presetup(current.task);
|
||||
task.preSetup(current.task);
|
||||
}
|
||||
|
||||
public final TableId tableId()
|
||||
|
|
@ -432,13 +434,19 @@ public class AccordCommandStore extends CommandStore
|
|||
return taskExecutor().tryExecuteImmediately(run);
|
||||
}
|
||||
|
||||
public AccordSafeCommandStore begin(AccordTask<?> operation, @Nullable CommandSummaries commandsForRanges)
|
||||
public AccordSafeCommandStore begin(AccordSafeCommandStore safeStore)
|
||||
{
|
||||
require(current == null);
|
||||
current = AccordSafeCommandStore.create(operation, commandsForRanges, this);
|
||||
current = safeStore;
|
||||
return current;
|
||||
}
|
||||
|
||||
public void complete(AccordSafeCommandStore store)
|
||||
{
|
||||
require(current == store);
|
||||
current = null;
|
||||
}
|
||||
|
||||
public boolean hasSafeStore()
|
||||
{
|
||||
return current != null;
|
||||
|
|
@ -454,19 +462,6 @@ public class AccordCommandStore extends CommandStore
|
|||
return progressLog;
|
||||
}
|
||||
|
||||
public void complete(AccordSafeCommandStore store)
|
||||
{
|
||||
require(current == store);
|
||||
current.postExecute();
|
||||
current = null;
|
||||
}
|
||||
|
||||
public void abort(AccordSafeCommandStore store)
|
||||
{
|
||||
Invariants.require(store == current);
|
||||
current = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown()
|
||||
{
|
||||
|
|
@ -650,7 +645,7 @@ public class AccordCommandStore extends CommandStore
|
|||
public Ready() { super(1); }
|
||||
@Override public void run() { decrement(); }
|
||||
|
||||
void maybeFlush(ExclusiveCaches caches, AccordCacheEntry<RoutingKey, CommandsForKey> e)
|
||||
void maybeFlush(ExclusiveCaches caches, AccordCacheEntry<RoutingKey, CommandsForKey, ?> e)
|
||||
{
|
||||
if (e.isModified())
|
||||
{
|
||||
|
|
@ -665,7 +660,7 @@ public class AccordCommandStore extends CommandStore
|
|||
{
|
||||
if (ranges == null)
|
||||
{
|
||||
for (AccordCacheEntry<RoutingKey, CommandsForKey> e : caches.commandsForKeys())
|
||||
for (AccordCacheEntry<RoutingKey, CommandsForKey, ?> e : caches.commandsForKeys())
|
||||
ready.maybeFlush(caches, e);
|
||||
}
|
||||
else
|
||||
|
|
@ -717,7 +712,7 @@ public class AccordCommandStore extends CommandStore
|
|||
if (!maybeShouldReplay(txnId))
|
||||
return AsyncChains.success(null);
|
||||
|
||||
return commandStore.chain(ExecutionContext.contextFor(txnId, "Replay"), safeStore -> {
|
||||
return commandStore.chain(ExecutionContext.unsequenced(txnId, "Replay"), safeStore -> {
|
||||
Replay replay = shouldReplay(txnId, safeStore.unsafeGet(txnId).current().participants());
|
||||
if (replay == Replay.NONE)
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -55,7 +55,6 @@ import org.apache.cassandra.journal.Descriptor;
|
|||
import org.apache.cassandra.schema.TableId;
|
||||
import org.apache.cassandra.service.accord.AccordCommandStore.DurablyAppliedTo;
|
||||
import org.apache.cassandra.service.accord.AccordExecutor.AccordExecutorFactory;
|
||||
import org.apache.cassandra.utils.FBUtilities;
|
||||
|
||||
import static org.apache.cassandra.config.AccordConfig.QueueShardModel.THREAD_PER_SHARD;
|
||||
import static org.apache.cassandra.config.DatabaseDescriptor.getAccord;
|
||||
|
|
@ -73,7 +72,6 @@ public class AccordCommandStores extends CommandStores implements CacheSize, Shu
|
|||
private final int mask;
|
||||
|
||||
private long cacheSize, workingSetSize;
|
||||
private int maxQueuedLoads, maxQueuedRangeLoads;
|
||||
private boolean shrinkingOn;
|
||||
|
||||
AccordCommandStores(NodeCommandStoreService node, Agent agent, DataStore store, RandomSource random,
|
||||
|
|
@ -87,12 +85,9 @@ public class AccordCommandStores extends CommandStores implements CacheSize, Shu
|
|||
|
||||
cacheSize = DatabaseDescriptor.getAccordCacheSizeInMiB() << 20;
|
||||
workingSetSize = DatabaseDescriptor.getAccordWorkingSetSizeInMiB() << 20;
|
||||
AccordConfig config = DatabaseDescriptor.getAccord();
|
||||
maxQueuedLoads = maxQueuedLoads(config);
|
||||
maxQueuedRangeLoads = maxQueuedRangeLoads(config);
|
||||
shrinkingOn = DatabaseDescriptor.getAccordCacheShrinkingOn();
|
||||
refreshCapacities();
|
||||
ScheduledExecutors.scheduledFastTasks.scheduleWithFixedDelay(() -> {
|
||||
ScheduledExecutors.scheduledTasks.scheduleWithFixedDelay(() -> {
|
||||
for (AccordExecutor executor : executors)
|
||||
{
|
||||
executor.executeDirectlyWithLock(() -> {
|
||||
|
|
@ -173,13 +168,6 @@ public class AccordCommandStores extends CommandStores implements CacheSize, Shu
|
|||
refreshCapacities();
|
||||
}
|
||||
|
||||
public synchronized void setMaxQueuedLoads(int total, int range)
|
||||
{
|
||||
maxQueuedLoads = total;
|
||||
maxQueuedRangeLoads = range;
|
||||
refreshCapacities();
|
||||
}
|
||||
|
||||
public synchronized void setShrinking(boolean on)
|
||||
{
|
||||
shrinkingOn = on;
|
||||
|
|
@ -213,14 +201,11 @@ public class AccordCommandStores extends CommandStores implements CacheSize, Shu
|
|||
{
|
||||
long capacityPerExecutor = cacheSize / executors.length;
|
||||
long workingSetPerExecutor = workingSetSize < 0 ? Long.MAX_VALUE : workingSetSize / executors.length;
|
||||
int maxLoadsPerExecutor = Math.max(1, (maxQueuedLoads + executors.length - 1) / executors.length);
|
||||
int maxRangeLoadsPerExecutor = Math.max(1, (maxQueuedRangeLoads + executors.length - 1) / executors.length);
|
||||
for (AccordExecutor executor : executors)
|
||||
{
|
||||
executor.executeDirectlyWithLock(() -> {
|
||||
executor.setCapacity(capacityPerExecutor);
|
||||
executor.setWorkingSetSize(workingSetPerExecutor);
|
||||
executor.setMaxQueuedLoads(maxLoadsPerExecutor, maxRangeLoadsPerExecutor);
|
||||
executor.cacheExclusive().setShrinkingOn(shrinkingOn);
|
||||
});
|
||||
}
|
||||
|
|
@ -341,21 +326,4 @@ public class AccordCommandStores extends CommandStores implements CacheSize, Shu
|
|||
return Math.max(1, config.queue_shard_count.or(DatabaseDescriptor.getAvailableProcessors() / 8));
|
||||
}
|
||||
}
|
||||
|
||||
private static int threads(AccordConfig config)
|
||||
{
|
||||
return config.queue_thread_count.or(2 * FBUtilities.getAvailableProcessors());
|
||||
}
|
||||
|
||||
public static int maxQueuedLoads(AccordConfig config)
|
||||
{
|
||||
return config.max_queued_loads.or(FBUtilities.getAvailableProcessors());
|
||||
}
|
||||
|
||||
public static int maxQueuedRangeLoads(AccordConfig config)
|
||||
{
|
||||
return config.max_queued_range_loads.or(maxQueuedLoads(config) / 4);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -26,7 +26,6 @@ import accord.utils.QuintConsumer;
|
|||
|
||||
import org.apache.cassandra.concurrent.CassandraThread;
|
||||
import org.apache.cassandra.service.accord.AccordExecutorLoops.LoopTask;
|
||||
import org.apache.cassandra.service.accord.debug.DebugExecution.DebugExecutorLoop;
|
||||
|
||||
import static org.apache.cassandra.service.accord.AccordExecutor.Mode.RUN_WITH_LOCK;
|
||||
import static org.apache.cassandra.service.accord.debug.DebugExecution.DEBUG_EXECUTION;
|
||||
|
|
@ -214,18 +213,21 @@ abstract class AccordExecutorAbstractLockLoop extends AccordExecutorAbstractLoop
|
|||
if (task != null)
|
||||
{
|
||||
self.setAccordActiveTask(task);
|
||||
boolean executed = false;
|
||||
try
|
||||
{
|
||||
task.preRunExclusive();
|
||||
executed = true;
|
||||
task.run();
|
||||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
task.fail(t);
|
||||
executed = false;
|
||||
task.failExecution(t);
|
||||
}
|
||||
finally
|
||||
{
|
||||
completeTaskExclusive(task);
|
||||
cleanupTaskExclusive(task, executed);
|
||||
self.setAccordActiveTask(null);
|
||||
}
|
||||
}
|
||||
|
|
@ -265,7 +267,6 @@ abstract class AccordExecutorAbstractLockLoop extends AccordExecutorAbstractLoop
|
|||
{
|
||||
return new LoopTask(name)
|
||||
{
|
||||
final DebugExecutorLoop debug = DEBUG_EXECUTION ? new DebugExecutorLoop(AccordExecutorAbstractLockLoop.this.debug) : null;
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
|
|
@ -276,17 +277,15 @@ abstract class AccordExecutorAbstractLockLoop extends AccordExecutorAbstractLoop
|
|||
Task task = null;
|
||||
while (true)
|
||||
{
|
||||
if (DEBUG_EXECUTION) debug.onLock();
|
||||
lock(self);
|
||||
try
|
||||
{
|
||||
if (DEBUG_EXECUTION) debug.onEnterLock();
|
||||
enterLockLoop();
|
||||
if (task != null)
|
||||
{
|
||||
Task tmp = task;
|
||||
task = null;
|
||||
completeTaskExclusive(tmp);
|
||||
cleanupTaskExclusive(tmp, true);
|
||||
self.setAccordActiveTask(null);
|
||||
}
|
||||
|
||||
|
|
@ -322,9 +321,9 @@ abstract class AccordExecutorAbstractLockLoop extends AccordExecutorAbstractLoop
|
|||
{
|
||||
if (task != null)
|
||||
{
|
||||
try { task.fail(t); }
|
||||
try { task.failExecution(t); }
|
||||
catch (Throwable t2) { t.addSuppressed(t2); }
|
||||
try { completeTaskExclusive(task); }
|
||||
try { cleanupTaskExclusive(task, false); }
|
||||
catch (Throwable t2) { t.addSuppressed(t2); }
|
||||
try { agent.onException(t); }
|
||||
catch (Throwable t2) { /* nothing we can sensibly do after already reporting */ }
|
||||
|
|
@ -350,7 +349,7 @@ abstract class AccordExecutorAbstractLockLoop extends AccordExecutorAbstractLoop
|
|||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
try { task.fail(t); }
|
||||
try { task.failExecution(t); }
|
||||
catch (Throwable t2)
|
||||
{
|
||||
try
|
||||
|
|
|
|||
|
|
@ -44,11 +44,6 @@ abstract class AccordExecutorAbstractLoop extends AccordExecutor
|
|||
return unqueued != null;
|
||||
}
|
||||
|
||||
Task unqueued()
|
||||
{
|
||||
return unqueued;
|
||||
}
|
||||
|
||||
final Task push(Task submit)
|
||||
{
|
||||
Invariants.require(submit.next == null);
|
||||
|
|
@ -89,26 +84,16 @@ abstract class AccordExecutorAbstractLoop extends AccordExecutor
|
|||
Invariants.require(cur != null);
|
||||
Task next = cur.next;
|
||||
cur.next = null;
|
||||
if (cur.isReadyToCleanup()) completeTaskExclusive(cur);
|
||||
else cur.submitExclusive(this);
|
||||
if (cur.is(Task.State.UNINITIALIZED)) cur.submitExclusive(this);
|
||||
else cleanupTaskExclusive(cur, true);
|
||||
return next;
|
||||
}
|
||||
|
||||
final Task enqueueOneCleanup(Task cur)
|
||||
final Task destructiveNext(Task cur)
|
||||
{
|
||||
Invariants.require(cur != null);
|
||||
Task next = cur.next;
|
||||
cur.next = null;
|
||||
completeTaskExclusive(cur);
|
||||
return next;
|
||||
}
|
||||
|
||||
final Task enqueueOneSubmit(Task cur)
|
||||
{
|
||||
Invariants.require(cur != null);
|
||||
Task next = cur.next;
|
||||
cur.next = null;
|
||||
cur.submitExclusive(this);
|
||||
return next;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,12 +33,14 @@ import org.apache.cassandra.service.accord.debug.DebugExecution.DebugTask;
|
|||
import org.apache.cassandra.utils.concurrent.SignalLock;
|
||||
|
||||
import static accord.utils.Invariants.nonNull;
|
||||
import static org.apache.cassandra.service.accord.AccordExecutor.Task.State.UNINITIALIZED;
|
||||
import static org.apache.cassandra.service.accord.debug.DebugExecution.DEBUG_EXECUTION;
|
||||
|
||||
public class AccordExecutorSignalLoop extends AccordExecutorAbstractLoop
|
||||
{
|
||||
private static class ShutdownException extends RuntimeException {}
|
||||
|
||||
private static final int MAX_LOOPS = 1000; // limit the amount of time we hold the lock for
|
||||
private final SignalLock lock;
|
||||
private final AccordExecutorLoops loops;
|
||||
private int readyToRunTarget = 1;
|
||||
|
|
@ -46,8 +48,7 @@ public class AccordExecutorSignalLoop extends AccordExecutorAbstractLoop
|
|||
// TODO (desired): intrusive queue using Task.next, but a little challenging because we reuse SequentialQueueTask so have ABA problem
|
||||
private final ConcurrentLinkedQueue<Task> readyToRun = new ConcurrentLinkedQueue<>();
|
||||
|
||||
private Task pendingSequentialHead, pendingSequentialTail;
|
||||
private Task pendingCleanupHead, pendingCleanupTail;
|
||||
private Task pendingExecutedHead, pendingExecutedTail;
|
||||
private Task pendingNewHead, pendingNewTail;
|
||||
private int pendingCount;
|
||||
|
||||
|
|
@ -105,14 +106,17 @@ public class AccordExecutorSignalLoop extends AccordExecutorAbstractLoop
|
|||
{
|
||||
updatePendingUnqueued();
|
||||
Task requeue = null, requeueTail = null;
|
||||
Task submit = null, submitTail = null;
|
||||
Task cur = pendingNewHead;
|
||||
while (cur != null)
|
||||
{
|
||||
Task next = cur.next;
|
||||
cur.next = null;
|
||||
if (cur.isNewWork())
|
||||
{
|
||||
cur.next = null;
|
||||
cur.submitExclusive(this);
|
||||
if (submit == null) submit = cur;
|
||||
else submitTail.next = cur;
|
||||
submitTail = cur;
|
||||
--pendingCount;
|
||||
}
|
||||
else
|
||||
|
|
@ -126,6 +130,13 @@ public class AccordExecutorSignalLoop extends AccordExecutorAbstractLoop
|
|||
|
||||
pendingNewHead = requeue;
|
||||
pendingNewTail = requeueTail;
|
||||
while (submit != null)
|
||||
{
|
||||
Task next = submit.next;
|
||||
submit.next = null;
|
||||
submit.submitExclusive(this);
|
||||
submit = next;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean enqueueOnePending()
|
||||
|
|
@ -133,25 +144,23 @@ public class AccordExecutorSignalLoop extends AccordExecutorAbstractLoop
|
|||
if (pendingCount == 0)
|
||||
return false;
|
||||
|
||||
if (pendingSequentialHead != null)
|
||||
--pendingCount;
|
||||
if (pendingExecutedHead != null)
|
||||
{
|
||||
pendingSequentialHead = enqueueOneCleanup(pendingSequentialHead);
|
||||
if (pendingSequentialHead == null)
|
||||
pendingSequentialTail = null;
|
||||
}
|
||||
else if (pendingCleanupHead != null)
|
||||
{
|
||||
pendingCleanupHead = enqueueOneCleanup(pendingCleanupHead);
|
||||
if (pendingCleanupHead == null)
|
||||
pendingCleanupTail = null;
|
||||
Task executed = pendingExecutedHead;
|
||||
pendingExecutedHead = destructiveNext(executed);
|
||||
if (pendingExecutedHead == null)
|
||||
pendingExecutedTail = null;
|
||||
cleanupTaskExclusive(executed, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
pendingNewHead = enqueueOneSubmit(pendingNewHead);
|
||||
Task submit = pendingNewHead;
|
||||
pendingNewHead = destructiveNext(submit);
|
||||
if (pendingNewHead == null)
|
||||
pendingNewTail = null;
|
||||
submit.submitExclusive(this);
|
||||
}
|
||||
--pendingCount;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -178,22 +187,29 @@ public class AccordExecutorSignalLoop extends AccordExecutorAbstractLoop
|
|||
}
|
||||
|
||||
boolean hasDrainedSignal = false;
|
||||
int loops = 0;
|
||||
while (true)
|
||||
{
|
||||
long state = lock.state();
|
||||
int signals = SignalLock.asyncSignalCount(state);
|
||||
int waiters = SignalLock.waitingEnabledThreadCount(state);
|
||||
if (signals >= readyToRunTarget)
|
||||
if (signals > 0)
|
||||
{
|
||||
if (enqueueOnePending() || (updatePendingUnqueued() && enqueueOnePending())) continue;
|
||||
else if (hasDrainedSignal)
|
||||
lock.signalLockWorkExclusive();
|
||||
return;
|
||||
}
|
||||
else if (waiters > 0 && signals > 1 && SignalLock.activeEnabledThreadCount(state) == 1)
|
||||
{
|
||||
// ensure at least one other thread is running if there's enough work for it; it will spin up other threads if necessary
|
||||
lock.propagateAsyncWorkSignals(1);
|
||||
if (++loops > MAX_LOOPS)
|
||||
return;
|
||||
|
||||
if (signals >= readyToRunTarget)
|
||||
{
|
||||
if (enqueueOnePending() || (updatePendingUnqueued() && enqueueOnePending())) continue;
|
||||
else if (hasDrainedSignal)
|
||||
lock.signalLockWorkExclusive();
|
||||
return;
|
||||
}
|
||||
else if (waiters > 0 && signals > 1 && SignalLock.activeEnabledThreadCount(state) == 1)
|
||||
{
|
||||
// ensure at least one other thread is running if there's enough work for it; it will spin up other threads if necessary
|
||||
lock.propagateAsyncWorkSignals(1);
|
||||
}
|
||||
}
|
||||
|
||||
Task task = pollAlreadyWaitingToRunExclusive();
|
||||
|
|
@ -216,9 +232,9 @@ public class AccordExecutorSignalLoop extends AccordExecutorAbstractLoop
|
|||
try { task.preRunExclusive(); }
|
||||
catch (Throwable t)
|
||||
{
|
||||
try { task.fail(t); }
|
||||
try { task.failExclusive(t, Task.State.FAILED_OTHER); }
|
||||
catch (Throwable t2) { try { t.addSuppressed(t2); } catch (Throwable t3) {} }
|
||||
try { completeTaskExclusive(task); }
|
||||
try { cleanupTaskExclusive(task, false); }
|
||||
catch (Throwable t2) { try { t.addSuppressed(t2); } catch (Throwable t3) {} }
|
||||
continue;
|
||||
}
|
||||
|
|
@ -237,28 +253,22 @@ public class AccordExecutorSignalLoop extends AccordExecutorAbstractLoop
|
|||
return false;
|
||||
|
||||
int count = 0;
|
||||
Task addSequentialHead = null, addSequentialTail = null;
|
||||
Task addCleanupHead = null, addCleanupTail = null;
|
||||
Task addExecutedHead = null, addExecutedTail = null;
|
||||
Task addNewHead = null, addNewTail = null;
|
||||
{
|
||||
Task cur = Task.reverse(acquireUnqueuedExclusive());
|
||||
while (cur != null)
|
||||
{
|
||||
Task next = cur.next;
|
||||
if (!cur.isReadyToCleanup())
|
||||
if (cur.is(UNINITIALIZED))
|
||||
{
|
||||
if (addNewHead == null) addNewHead = addNewTail = setNextNull(cur);
|
||||
else addNewHead = reverseOne(addNewHead, cur);
|
||||
}
|
||||
else if (cur instanceof ExclusiveExecutorTask)
|
||||
{
|
||||
if (addSequentialHead == null) addSequentialHead = addSequentialTail = setNextNull(cur);
|
||||
else addSequentialHead = reverseOne(addSequentialHead, cur);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (addCleanupHead == null) addCleanupHead = addCleanupTail = setNextNull(cur);
|
||||
else addCleanupHead = reverseOne(addCleanupHead, cur);
|
||||
if (addExecutedHead == null) addExecutedHead = addExecutedTail = setNextNull(cur);
|
||||
else addExecutedHead = reverseOne(addExecutedHead, cur);
|
||||
}
|
||||
++count;
|
||||
cur = next;
|
||||
|
|
@ -266,17 +276,11 @@ public class AccordExecutorSignalLoop extends AccordExecutorAbstractLoop
|
|||
}
|
||||
|
||||
pendingCount += count;
|
||||
if (addSequentialHead != null)
|
||||
if (addExecutedHead != null)
|
||||
{
|
||||
if (pendingSequentialHead == null) pendingSequentialHead = addSequentialHead;
|
||||
else pendingSequentialTail.next = addSequentialHead;
|
||||
pendingSequentialTail = addSequentialTail;
|
||||
}
|
||||
if (addCleanupHead != null)
|
||||
{
|
||||
if (pendingCleanupHead == null) pendingCleanupHead = addCleanupHead;
|
||||
else pendingCleanupTail.next = addCleanupHead;
|
||||
pendingCleanupTail = addCleanupTail;
|
||||
if (pendingExecutedHead == null) pendingExecutedHead = addExecutedHead;
|
||||
else pendingExecutedTail.next = addExecutedHead;
|
||||
pendingExecutedTail = addExecutedTail;
|
||||
}
|
||||
if (addNewHead != null)
|
||||
{
|
||||
|
|
@ -349,22 +353,22 @@ public class AccordExecutorSignalLoop extends AccordExecutorAbstractLoop
|
|||
}
|
||||
}
|
||||
|
||||
private Task cleanupAndMaybeGetWork(AccordTaskRunner self, @Nullable Task cleanup)
|
||||
private Task executedAndMaybeGetWork(AccordTaskRunner self, @Nullable Task executed)
|
||||
{
|
||||
if (lock.tryAcquireAsyncWork())
|
||||
{
|
||||
if (shutdown)
|
||||
throw new ShutdownException();
|
||||
|
||||
return pushCleanupAndReturn(cleanup, nonNull(pollReadyToRun()));
|
||||
return pushExecutedAndReturn(executed, nonNull(pollReadyToRun()));
|
||||
}
|
||||
|
||||
if (!tryLock(self))
|
||||
return pushCleanupAndReturn(cleanup, null);
|
||||
return pushExecutedAndReturn(executed, null);
|
||||
|
||||
try
|
||||
{
|
||||
completeTaskExclusive(cleanup);
|
||||
cleanupTaskExclusive(executed, true);
|
||||
fetchWorkExclusive();
|
||||
}
|
||||
catch (Throwable t)
|
||||
|
|
@ -383,10 +387,9 @@ public class AccordExecutorSignalLoop extends AccordExecutorAbstractLoop
|
|||
return null;
|
||||
}
|
||||
|
||||
private Task pushCleanupAndReturn(Task cleanup, Task result)
|
||||
private Task pushExecutedAndReturn(Task complete, Task result)
|
||||
{
|
||||
cleanup.setReadyToCleanup();
|
||||
if (push(cleanup) == null)
|
||||
if (push(complete) == null)
|
||||
lock.signalLockWork();
|
||||
return result;
|
||||
}
|
||||
|
|
@ -401,7 +404,7 @@ public class AccordExecutorSignalLoop extends AccordExecutorAbstractLoop
|
|||
|
||||
final boolean unlockAndAcquire(AccordTaskRunner self)
|
||||
{
|
||||
self.clearAccordLockedExecutor();
|
||||
self.exitAccordLockedExecutor();
|
||||
if (DEBUG_EXECUTION) debug.onExitLock();
|
||||
return lock.unlockAndAcquireAsyncWork();
|
||||
}
|
||||
|
|
@ -422,7 +425,7 @@ public class AccordExecutorSignalLoop extends AccordExecutorAbstractLoop
|
|||
{
|
||||
if (task != null)
|
||||
{
|
||||
try { task = cleanupAndMaybeGetWork(self, task); }
|
||||
try { task = executedAndMaybeGetWork(self, task); }
|
||||
catch (Throwable t) { task = null; throw t; }
|
||||
}
|
||||
if (task == null)
|
||||
|
|
@ -435,7 +438,7 @@ public class AccordExecutorSignalLoop extends AccordExecutorAbstractLoop
|
|||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
try { task.fail(t); }
|
||||
try { task.failExecution(t); }
|
||||
catch (Throwable t2)
|
||||
{
|
||||
try
|
||||
|
|
|
|||
|
|
@ -82,7 +82,8 @@ class AccordExecutorSimple extends AccordExecutor
|
|||
|
||||
protected void run()
|
||||
{
|
||||
Thread self = Thread.currentThread();
|
||||
AccordTaskRunner self = AccordTaskRunner.get();
|
||||
self.setAccordActiveExecutor(AccordExecutorSimple.this);
|
||||
lock.lock();
|
||||
try
|
||||
{
|
||||
|
|
@ -95,11 +96,24 @@ class AccordExecutorSimple extends AccordExecutor
|
|||
return;
|
||||
}
|
||||
|
||||
try { task.preRunExclusive(); task.run(); }
|
||||
catch (Throwable t) { task.fail(t); }
|
||||
// TODO (expected): dedup with AbstractLockLoop, and cleanup (executed flag is a bit ugly)
|
||||
self.setAccordActiveTask(task);
|
||||
boolean executed = false;
|
||||
try
|
||||
{
|
||||
task.preRunExclusive();
|
||||
executed = true;
|
||||
task.run();
|
||||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
executed = false;
|
||||
task.failExecution(t);
|
||||
}
|
||||
finally
|
||||
{
|
||||
completeTaskExclusive(task);
|
||||
cleanupTaskExclusive(task, executed);
|
||||
self.setAccordActiveTask(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,51 +20,22 @@ package org.apache.cassandra.service.accord;
|
|||
|
||||
import java.util.Objects;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
import accord.api.Journal;
|
||||
import accord.local.Command;
|
||||
import accord.local.SafeCommand;
|
||||
import accord.primitives.TxnId;
|
||||
|
||||
import org.apache.cassandra.utils.concurrent.Ref;
|
||||
import org.apache.cassandra.service.accord.AccordCacheEntry.LockMode;
|
||||
|
||||
public class AccordSafeCommand extends SafeCommand implements AccordSafeState<TxnId, Command>
|
||||
public class AccordSafeCommand extends SafeCommand implements AccordSafeState<TxnId, Command, AccordSafeCommand>
|
||||
{
|
||||
public static class DebugAccordSafeCommand extends AccordSafeCommand
|
||||
{
|
||||
final Ref<?> selfRef;
|
||||
public DebugAccordSafeCommand(AccordCacheEntry<TxnId, Command> global)
|
||||
{
|
||||
super(global);
|
||||
selfRef = new Ref<>(this, null);
|
||||
selfRef.debug(global.key().toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markUnsafe()
|
||||
{
|
||||
super.markUnsafe();
|
||||
selfRef.release();
|
||||
}
|
||||
|
||||
public static void trace(AccordSafeCommand safeCommand, String message)
|
||||
{
|
||||
((DebugAccordSafeCommand)safeCommand).selfRef.debug(message);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean unsafe;
|
||||
private final AccordCacheEntry<TxnId, Command> global;
|
||||
private final AccordCacheEntry<TxnId, Command, AccordSafeCommand> global;
|
||||
private Command original;
|
||||
private Command current;
|
||||
|
||||
public AccordSafeCommand(AccordCacheEntry<TxnId, Command> global)
|
||||
public AccordSafeCommand(AccordCacheEntry<TxnId, Command, AccordSafeCommand> global)
|
||||
{
|
||||
super(global.key());
|
||||
this.global = global;
|
||||
this.original = null;
|
||||
this.current = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -73,7 +44,7 @@ public class AccordSafeCommand extends SafeCommand implements AccordSafeState<Tx
|
|||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
AccordSafeCommand that = (AccordSafeCommand) o;
|
||||
return Objects.equals(original, that.original) && Objects.equals(current, that.current);
|
||||
return Objects.equals(this.original, that.original) && Objects.equals(this.current(), that.current());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -86,66 +57,36 @@ public class AccordSafeCommand extends SafeCommand implements AccordSafeState<Tx
|
|||
public String toString()
|
||||
{
|
||||
return "AccordSafeCommand{" +
|
||||
"invalidated=" + unsafe +
|
||||
"status=" + statusString() +
|
||||
", global=" + global +
|
||||
", original=" + original +
|
||||
", current=" + current +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccordCacheEntry<TxnId, Command> global()
|
||||
public AccordCacheEntry<TxnId, Command, AccordSafeCommand> global()
|
||||
{
|
||||
checkNotInvalidated();
|
||||
return global;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Command current()
|
||||
public void postExecute(AccordTask<?> owner)
|
||||
{
|
||||
checkNotInvalidated();
|
||||
return current;
|
||||
}
|
||||
|
||||
@Override
|
||||
@VisibleForTesting
|
||||
public void set(Command command)
|
||||
{
|
||||
checkNotInvalidated();
|
||||
this.current = command;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Command original()
|
||||
{
|
||||
checkNotInvalidated();
|
||||
return original;
|
||||
global.releaseExclusive(this, owner);
|
||||
}
|
||||
|
||||
public Journal.CommandUpdate update()
|
||||
{
|
||||
return new Journal.CommandUpdate(original, current);
|
||||
return new Journal.CommandUpdate(original, current());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preExecute()
|
||||
public void preExecute(AccordTask<?> owner, LockMode lockMode)
|
||||
{
|
||||
checkNotInvalidated();
|
||||
original = global.getExclusive();
|
||||
requireUninitialised();
|
||||
original = global.lockExclusive(owner, lockMode);
|
||||
current = original;
|
||||
if (isUnset())
|
||||
uninitialised();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markUnsafe()
|
||||
{
|
||||
unsafe = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUnsafe()
|
||||
{
|
||||
return unsafe;
|
||||
if (current == null)
|
||||
initialise();
|
||||
setSafe();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,13 +18,8 @@
|
|||
|
||||
package org.apache.cassandra.service.accord;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
import accord.api.Agent;
|
||||
|
|
@ -35,16 +30,20 @@ import accord.api.RoutingKey;
|
|||
import accord.impl.AbstractSafeCommandStore;
|
||||
import accord.local.Command;
|
||||
import accord.local.CommandStores;
|
||||
import accord.local.CommandSummaries;
|
||||
import accord.local.ExecutionContext;
|
||||
import accord.local.NodeCommandStoreService;
|
||||
import accord.local.RedundantBefore;
|
||||
import accord.local.SafeState;
|
||||
import accord.local.cfk.CommandsForKey;
|
||||
import accord.local.cfk.SafeCommandsForKey;
|
||||
import accord.primitives.AbstractUnseekableKeys;
|
||||
import accord.primitives.Routables;
|
||||
import accord.primitives.Timestamp;
|
||||
import accord.primitives.Txn.Kind.Kinds;
|
||||
import accord.primitives.TxnId;
|
||||
import accord.primitives.Unseekables;
|
||||
import accord.utils.Invariants;
|
||||
import accord.utils.UnhandledEnum;
|
||||
|
||||
import org.apache.cassandra.metrics.LogLinearDecayingHistograms;
|
||||
import org.apache.cassandra.service.accord.AccordCommandStore.ExclusiveCaches;
|
||||
|
|
@ -53,69 +52,53 @@ import org.apache.cassandra.service.accord.AccordDurableOnFlush.ReportDurable;
|
|||
import org.apache.cassandra.service.paxos.PaxosState;
|
||||
|
||||
import static accord.utils.Invariants.illegalState;
|
||||
import static org.apache.cassandra.service.accord.AccordCacheEntry.LockMode.UNQUEUED;
|
||||
|
||||
public class AccordSafeCommandStore extends AbstractSafeCommandStore<AccordSafeCommand, AccordSafeCommandsForKey, AccordCommandStore.ExclusiveCaches>
|
||||
public final class AccordSafeCommandStore extends AbstractSafeCommandStore<AccordSafeCommand, AccordSafeCommandsForKey, AccordCommandStore.ExclusiveCaches>
|
||||
{
|
||||
final AccordTask<?> task;
|
||||
private final @Nullable CommandSummaries commandsForRanges;
|
||||
private final AccordCommandStore commandStore;
|
||||
|
||||
private AccordSafeCommandStore(AccordTask<?> task,
|
||||
@Nullable CommandSummaries commandsForRanges,
|
||||
AccordCommandStore commandStore)
|
||||
AccordSafeCommandStore(AccordTask<?> task, ExecutionContext context)
|
||||
{
|
||||
super(task.executionContext(), commandStore);
|
||||
super(context);
|
||||
this.task = task;
|
||||
this.commandsForRanges = commandsForRanges;
|
||||
this.commandStore = commandStore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandStores.RangesForEpoch ranges()
|
||||
{
|
||||
CommandStores.RangesForEpoch ranges = super.ranges();
|
||||
if (ranges != null)
|
||||
return ranges;
|
||||
|
||||
return commandStore.unsafeGetRangesForEpoch();
|
||||
}
|
||||
|
||||
public static AccordSafeCommandStore create(AccordTask<?> task,
|
||||
@Nullable CommandSummaries commandsForRanges,
|
||||
AccordCommandStore commandStore)
|
||||
{
|
||||
return new AccordSafeCommandStore(task, commandsForRanges, commandStore);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public Set<RoutingKey> commandsForKeysKeys()
|
||||
public Iterable<SafeCommandsForKey> safeCommandsForKeys()
|
||||
{
|
||||
if (task.commandsForKey() == null)
|
||||
return Collections.emptySet();
|
||||
return task.commandsForKey().keySet();
|
||||
return task.refs.values().stream()
|
||||
.filter(v -> v instanceof SafeCommandsForKey)
|
||||
.map(v -> (SafeCommandsForKey)v)::iterator;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AccordSafeCommand getInternal(TxnId txnId)
|
||||
{
|
||||
Map<TxnId, AccordSafeCommand> commands = task.commands();
|
||||
if (commands == null)
|
||||
return null;
|
||||
return commands.get(txnId);
|
||||
return (AccordSafeCommand) task.refs.get(txnId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ExclusiveCaches tryGetCaches()
|
||||
{
|
||||
return commandStore.tryLockCaches();
|
||||
if (task.isIncremental())
|
||||
{
|
||||
// We could relax this, but requires careful consideration of semantics when:
|
||||
// - touching keys we have scheduled to process later
|
||||
// - touching txnIds we may have to logically lock until the whole processing completes to
|
||||
// ensure persistent state machine updates are consistent with incremental updates to cache
|
||||
return null;
|
||||
}
|
||||
return commandStore().tryLockCaches();
|
||||
}
|
||||
|
||||
protected AccordSafeCommand add(AccordSafeCommand safeCommand, ExclusiveCaches caches)
|
||||
{
|
||||
Object check = task.ensureCommands().putIfAbsent(safeCommand.txnId(), safeCommand);
|
||||
Object check = task.refs.putIfAbsent(safeCommand.txnId(), safeCommand);
|
||||
if (check == null)
|
||||
{
|
||||
safeCommand.preExecute();
|
||||
Invariants.require(!task.isIncremental()); // if isIncremental we'll have to supply holdQueue==true, but for now we forbid it
|
||||
safeCommand.preExecute(task, UNQUEUED);
|
||||
return safeCommand;
|
||||
}
|
||||
else
|
||||
|
|
@ -131,7 +114,7 @@ public class AccordSafeCommandStore extends AbstractSafeCommandStore<AccordSafeC
|
|||
// Field persistence is handled by AccordTask
|
||||
}
|
||||
|
||||
protected void persistFieldUpdatesInternal(Runnable onDone)
|
||||
void persistFieldUpdatesInternal(Runnable onDone)
|
||||
{
|
||||
FieldUpdates updates = fieldUpdates();
|
||||
if (updates == null)
|
||||
|
|
@ -142,7 +125,7 @@ public class AccordSafeCommandStore extends AbstractSafeCommandStore<AccordSafeC
|
|||
long ticket = AccordCommandStore.nextSafeRedundantBeforeTicket.incrementAndGet();
|
||||
SafeRedundantBefore update = new SafeRedundantBefore(ticket, updates.newRedundantBefore);
|
||||
Runnable reportRedundantBefore = () -> {
|
||||
AccordCommandStore.safeRedundantBeforeUpdater.accumulateAndGet(commandStore, update, SafeRedundantBefore::max);
|
||||
AccordCommandStore.safeRedundantBeforeUpdater.accumulateAndGet(commandStore(), update, SafeRedundantBefore::max);
|
||||
};
|
||||
Runnable prevOnDone = onDone;
|
||||
onDone = prevOnDone == null ? reportRedundantBefore : () -> {
|
||||
|
|
@ -150,15 +133,15 @@ public class AccordSafeCommandStore extends AbstractSafeCommandStore<AccordSafeC
|
|||
finally { prevOnDone.run(); }
|
||||
};
|
||||
}
|
||||
commandStore.persistFieldUpdates(updates, onDone);
|
||||
commandStore().persistFieldUpdates(updates, onDone);
|
||||
}
|
||||
|
||||
protected AccordSafeCommandsForKey add(AccordSafeCommandsForKey safeCfk, ExclusiveCaches caches)
|
||||
{
|
||||
Object check = task.ensureCommandsForKey().putIfAbsent(safeCfk.key(), safeCfk);
|
||||
Object check = task.refs.putIfAbsent(safeCfk.key(), safeCfk);
|
||||
if (check == null)
|
||||
{
|
||||
safeCfk.preExecute();
|
||||
safeCfk.preExecute(task, UNQUEUED);
|
||||
return safeCfk;
|
||||
}
|
||||
else
|
||||
|
|
@ -171,17 +154,17 @@ public class AccordSafeCommandStore extends AbstractSafeCommandStore<AccordSafeC
|
|||
@Override
|
||||
protected AccordSafeCommandsForKey getInternal(RoutingKey key)
|
||||
{
|
||||
Map<RoutingKey, AccordSafeCommandsForKey> commandsForKey = task.commandsForKey();
|
||||
if (commandsForKey == null)
|
||||
AccordSafeCommandsForKey safeCfk = (AccordSafeCommandsForKey) task.refs.get(key);
|
||||
if (safeCfk == null || safeCfk.isUninitialised())
|
||||
return null;
|
||||
return commandsForKey.get(key);
|
||||
return safeCfk;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRangesForEpoch(CommandStores.RangesForEpoch rangesForEpoch)
|
||||
{
|
||||
super.setRangesForEpoch(rangesForEpoch);
|
||||
commandStore.updateMinHlc(PaxosState.ballotTracker().getLowBound().unixMicros() + 1);
|
||||
commandStore().updateMinHlc(PaxosState.ballotTracker().getLowBound().unixMicros() + 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -194,13 +177,13 @@ public class AccordSafeCommandStore extends AbstractSafeCommandStore<AccordSafeC
|
|||
public void reportDurable(RedundantBefore addRedundantBefore, int flags)
|
||||
{
|
||||
upsertRedundantBefore(addRedundantBefore);
|
||||
commandStore.maybeTerminated(ReportDurable.isCommandStoreFlush(flags), ReportDurable.isDataStoreFlush(flags));
|
||||
commandStore().maybeTerminated(ReportDurable.isCommandStoreFlush(flags), ReportDurable.isDataStoreFlush(flags));
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccordCommandStore commandStore()
|
||||
{
|
||||
return commandStore;
|
||||
return task.commandStore;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -212,7 +195,7 @@ public class AccordSafeCommandStore extends AbstractSafeCommandStore<AccordSafeC
|
|||
@Override
|
||||
public Agent agent()
|
||||
{
|
||||
return commandStore.agent();
|
||||
return commandStore().agent();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -224,16 +207,16 @@ public class AccordSafeCommandStore extends AbstractSafeCommandStore<AccordSafeC
|
|||
@Override
|
||||
public NodeCommandStoreService node()
|
||||
{
|
||||
return commandStore.node();
|
||||
return commandStore().node();
|
||||
}
|
||||
|
||||
public LogLinearDecayingHistograms.Buffer histogramBuffer()
|
||||
{
|
||||
if (task.histogramBuffer == null)
|
||||
{
|
||||
task.histogramBuffer = commandStore.metricsBuffer;
|
||||
task.histogramBuffer = commandStore().metricsBuffer;
|
||||
if (task.histogramBuffer == null)
|
||||
task.histogramBuffer = commandStore.metricsBuffer = new LogLinearDecayingHistograms.Buffer(commandStore.executor().histograms);
|
||||
task.histogramBuffer = commandStore().metricsBuffer = new LogLinearDecayingHistograms.Buffer(commandStore().executor().histograms);
|
||||
Invariants.require(task.histogramBuffer.isEmpty());
|
||||
}
|
||||
return task.histogramBuffer;
|
||||
|
|
@ -241,35 +224,48 @@ public class AccordSafeCommandStore extends AbstractSafeCommandStore<AccordSafeC
|
|||
|
||||
private boolean visitForKey(Unseekables<?> keysOrRanges, Predicate<CommandsForKey> forEach)
|
||||
{
|
||||
Map<RoutingKey, AccordSafeCommandsForKey> commandsForKey = task.commandsForKey;
|
||||
if (commandsForKey == null)
|
||||
return true;
|
||||
|
||||
Unseekables<?> skip = context.keys().without(keysOrRanges);
|
||||
for (SafeCommandsForKey safeCfk : commandsForKey.values())
|
||||
Unseekables<?> unseekables = context.keys();
|
||||
switch (unseekables.domain())
|
||||
{
|
||||
if (skip.contains(safeCfk.key()))
|
||||
continue;
|
||||
default: throw new UnhandledEnum(unseekables.domain());
|
||||
case Key:
|
||||
AbstractUnseekableKeys keys = (AbstractUnseekableKeys) context.keys();
|
||||
return Routables.foldl(keys, keysOrRanges, (self, f, key, v, index) -> {
|
||||
SafeCommandsForKey safeCfk = (SafeCommandsForKey) self.task.refs.get(key);
|
||||
return f.test(safeCfk.current());
|
||||
}, this, forEach, Boolean.TRUE, cont -> !cont);
|
||||
|
||||
if (!forEach.test(safeCfk.current()))
|
||||
return false;
|
||||
case Range:
|
||||
Unseekables<?> skip = context.keys().without(keysOrRanges);
|
||||
for (SafeState<?> safeState : task.refs.values())
|
||||
{
|
||||
if (!(safeState instanceof AccordSafeCommandsForKey))
|
||||
continue;
|
||||
|
||||
SafeCommandsForKey safeCfk = (SafeCommandsForKey) safeState;
|
||||
if (skip.contains(safeCfk.key()))
|
||||
continue;
|
||||
|
||||
if (!forEach.test(safeCfk.current()))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <P1, P2> void visit(Unseekables<?> keysOrRanges, Timestamp startedBefore, Kinds testKind, ActiveCommandVisitor<P1, P2> visitor, P1 p1, P2 p2)
|
||||
{
|
||||
visitForKey(keysOrRanges, cfk -> { cfk.visit(startedBefore, testKind, visitor, p1, p2); return true; });
|
||||
if (commandsForRanges != null)
|
||||
commandsForRanges.visit(keysOrRanges, startedBefore, testKind, visitor, p1, p2);
|
||||
if (task.commandsForRanges != null)
|
||||
task.commandsForRanges.visit(keysOrRanges, startedBefore, testKind, visitor, p1, p2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(Unseekables<?> keysOrRanges, TxnId testTxnId, Kinds testKind, SupersedingCommandVisitor visit)
|
||||
{
|
||||
return visitForKey(keysOrRanges, cfk -> cfk.visit(testTxnId, testKind, visit))
|
||||
&& (commandsForRanges == null || commandsForRanges.visit(keysOrRanges, testTxnId, testKind, visit));
|
||||
&& (task.commandsForRanges == null || task.commandsForRanges.visit(keysOrRanges, testTxnId, testKind, visit));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -20,38 +20,32 @@ package org.apache.cassandra.service.accord;
|
|||
|
||||
import java.util.Objects;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
import accord.api.RoutingKey;
|
||||
import accord.local.cfk.CommandsForKey;
|
||||
import accord.local.cfk.NotifySink;
|
||||
import accord.local.cfk.SafeCommandsForKey;
|
||||
import accord.utils.Invariants;
|
||||
|
||||
public class AccordSafeCommandsForKey extends SafeCommandsForKey implements AccordSafeState<RoutingKey, CommandsForKey>
|
||||
import org.apache.cassandra.service.accord.AccordCacheEntry.LockMode;
|
||||
|
||||
public class AccordSafeCommandsForKey extends SafeCommandsForKey implements AccordSafeState<RoutingKey, CommandsForKey, AccordSafeCommandsForKey>
|
||||
{
|
||||
public static class CommandsForKeyCacheEntry extends AccordCacheEntry<RoutingKey, CommandsForKey>
|
||||
public static class CommandsForKeyCacheEntry extends AccordCacheEntry<RoutingKey, CommandsForKey, AccordSafeCommandsForKey>
|
||||
{
|
||||
private NotifySink overrideSink;
|
||||
|
||||
CommandsForKeyCacheEntry(RoutingKey key, AccordCache.Type<RoutingKey, CommandsForKey, ?>.Instance owner)
|
||||
CommandsForKeyCacheEntry(RoutingKey key, AccordCache.Type<RoutingKey, CommandsForKey, AccordSafeCommandsForKey>.Instance owner)
|
||||
{
|
||||
super(key, owner);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean invalidated;
|
||||
private final AccordCacheEntry<RoutingKey, CommandsForKey> global;
|
||||
private CommandsForKey original;
|
||||
private CommandsForKey current;
|
||||
private final AccordCacheEntry<RoutingKey, CommandsForKey, AccordSafeCommandsForKey> global;
|
||||
|
||||
public AccordSafeCommandsForKey(AccordCacheEntry<RoutingKey, CommandsForKey> global)
|
||||
public AccordSafeCommandsForKey(AccordCacheEntry<RoutingKey, CommandsForKey, AccordSafeCommandsForKey> global)
|
||||
{
|
||||
super(global.key());
|
||||
this.global = global;
|
||||
this.original = null;
|
||||
this.current = null;
|
||||
// if (overrideSink() == null)
|
||||
// overrideSink(new RecordingNotifySink());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -60,7 +54,7 @@ public class AccordSafeCommandsForKey extends SafeCommandsForKey implements Acco
|
|||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
AccordSafeCommandsForKey that = (AccordSafeCommandsForKey) o;
|
||||
return Objects.equals(original, that.original) && Objects.equals(current, that.current);
|
||||
return Objects.equals(current, that.current);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -73,47 +67,21 @@ public class AccordSafeCommandsForKey extends SafeCommandsForKey implements Acco
|
|||
public String toString()
|
||||
{
|
||||
return "AccordSafeCommandsForKey{" +
|
||||
"invalidated=" + invalidated +
|
||||
"state=" + statusString() +
|
||||
", global=" + global +
|
||||
", original=" + original +
|
||||
", current=" + current +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasUpdate()
|
||||
public final AccordCacheEntry<RoutingKey, CommandsForKey, AccordSafeCommandsForKey> global()
|
||||
{
|
||||
boolean hasUpdate = AccordSafeState.super.hasUpdate();
|
||||
|
||||
// cfk initialization is legal, but doesn't need to be propagated to the cache (and would
|
||||
// cause an exception to be thrown if it were). Making an exception on the cache side could
|
||||
// throw away applied cfk updates as well, so it's special cased here
|
||||
if (hasUpdate && original == null && current != null && current.size() == 0)
|
||||
return false;
|
||||
|
||||
return hasUpdate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccordCacheEntry<RoutingKey, CommandsForKey> global()
|
||||
{
|
||||
checkNotInvalidated();
|
||||
return global;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandsForKey current()
|
||||
public void postExecute(AccordTask<?> owner)
|
||||
{
|
||||
checkNotInvalidated();
|
||||
return current;
|
||||
}
|
||||
|
||||
@Override
|
||||
@VisibleForTesting
|
||||
public void set(CommandsForKey cfk)
|
||||
{
|
||||
checkNotInvalidated();
|
||||
this.current = cfk;
|
||||
global.releaseExclusive(this, owner);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -128,31 +96,13 @@ public class AccordSafeCommandsForKey extends SafeCommandsForKey implements Acco
|
|||
return ((CommandsForKeyCacheEntry)global).overrideSink;
|
||||
}
|
||||
|
||||
public CommandsForKey original()
|
||||
public void preExecute(AccordTask<?> owner, LockMode lockMode)
|
||||
{
|
||||
checkNotInvalidated();
|
||||
return original;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preExecute()
|
||||
{
|
||||
checkNotInvalidated();
|
||||
original = global.getExclusive();
|
||||
current = original;
|
||||
if (isUnset())
|
||||
requireUninitialised();
|
||||
current = global.lockExclusive(owner, lockMode);
|
||||
if (current == null)
|
||||
initialize();
|
||||
setSafe();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markUnsafe()
|
||||
{
|
||||
invalidated = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUnsafe()
|
||||
{
|
||||
return invalidated;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,41 +17,31 @@
|
|||
*/
|
||||
package org.apache.cassandra.service.accord;
|
||||
|
||||
import accord.impl.SafeState;
|
||||
import accord.local.SafeState;
|
||||
|
||||
public interface AccordSafeState<K, V> extends SafeState<V>
|
||||
import org.apache.cassandra.service.accord.AccordCacheEntry.LockMode;
|
||||
|
||||
public interface AccordSafeState<K, V, S extends SafeState<V> & AccordSafeState<K, V, S>>
|
||||
{
|
||||
void set(V update);
|
||||
V original();
|
||||
void markUnsafe();
|
||||
boolean isUnsafe();
|
||||
void preExecute();
|
||||
AccordCacheEntry<K, V, S> global();
|
||||
void postExecute(AccordTask<?> owner);
|
||||
void preExecute(AccordTask<?> owner, LockMode lockMode);
|
||||
|
||||
AccordCacheEntry<K, V> global();
|
||||
|
||||
default boolean hasUpdate()
|
||||
static AccordCacheEntry<?, ?, ?> global(SafeState<?> safeState)
|
||||
{
|
||||
return original() != current();
|
||||
return safeState.getClass() == AccordSafeCommand.class ? ((AccordSafeCommand) safeState).global()
|
||||
: ((AccordSafeCommandsForKey) safeState).global();
|
||||
}
|
||||
|
||||
default void revert()
|
||||
static void postExecute(SafeState<?> safeState, AccordTask<?> owner)
|
||||
{
|
||||
set(original());
|
||||
if (safeState.getClass() == AccordSafeCommand.class) ((AccordSafeCommand) safeState).postExecute(owner);
|
||||
else ((AccordSafeCommandsForKey) safeState).postExecute(owner);
|
||||
}
|
||||
|
||||
default K key()
|
||||
static void preExecute(SafeState<?> safeState, AccordTask<?> owner, LockMode lockMode)
|
||||
{
|
||||
return global().key();
|
||||
}
|
||||
|
||||
default Throwable failure()
|
||||
{
|
||||
return global().failure();
|
||||
}
|
||||
|
||||
default void checkNotInvalidated()
|
||||
{
|
||||
if (isUnsafe())
|
||||
throw new IllegalStateException("Cannot access invalidated " + this);
|
||||
if (safeState.getClass() == AccordSafeCommand.class) ((AccordSafeCommand) safeState).preExecute(owner, lockMode);
|
||||
else ((AccordSafeCommandsForKey) safeState).preExecute(owner, lockMode);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -464,6 +464,7 @@ public class AccordService implements IAccordService, Shutdownable
|
|||
agent.setup(localId);
|
||||
AccordTimeService time = new AccordTimeService();
|
||||
this.scheduler = new AccordScheduler();
|
||||
// TODO (expected): can we pass ImmediateExecutor rather than Scheduler?
|
||||
final RequestCallbacks callbacks = new RequestCallbacks(time, scheduler);
|
||||
this.dataStore = new AccordDataStore();
|
||||
this.journal = new AccordJournal(DatabaseDescriptor.getAccord().journal);
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -88,7 +88,7 @@ public class InMemoryRangeIndex extends InMemoryRangeSummaryIndex implements Ran
|
|||
{
|
||||
for (TxnId txnId : load)
|
||||
{
|
||||
AccordCacheEntry<TxnId, Command> entry = caches.commands().getUnsafe(txnId);
|
||||
AccordCacheEntry<TxnId, Command, ?> entry = caches.commands().getUnsafe(txnId);
|
||||
if (entry == null)
|
||||
{
|
||||
loadFromDisk.add(txnId);
|
||||
|
|
@ -115,7 +115,6 @@ public class InMemoryRangeIndex extends InMemoryRangeSummaryIndex implements Ran
|
|||
|
||||
public void finish(Map<Timestamp, Summary> into)
|
||||
{
|
||||
cleanupExclusive(null);
|
||||
owner.search(this, into::put, null);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ public interface RangeIndex
|
|||
return null;
|
||||
}
|
||||
|
||||
public CommandSummaries.Summary ifRelevant(AccordCacheEntry<TxnId, Command> state)
|
||||
public CommandSummaries.Summary ifRelevant(AccordCacheEntry<TxnId, Command, ?> state)
|
||||
{
|
||||
if (state.key().domain() != Routable.Domain.Range)
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -178,7 +178,7 @@ public class DebugBlockedTxns
|
|||
|
||||
private AsyncChain<Txn> visitRootTxnAsync(CommandStore commandStore, TxnId txnId)
|
||||
{
|
||||
return commandStore.chain(ExecutionContext.contextFor(txnId, "Populate txn_blocked_by"), safeStore -> {
|
||||
return commandStore.chain(ExecutionContext.unsequenced(txnId, "Populate txn_blocked_by"), safeStore -> {
|
||||
Command command = safeStore.unsafeGetNoCleanup(txnId).current();
|
||||
if (command == null || command.saveStatus() == SaveStatus.Uninitialised)
|
||||
return null;
|
||||
|
|
@ -188,7 +188,7 @@ public class DebugBlockedTxns
|
|||
|
||||
private AsyncChain<Txn> visitTxnAsync(CommandStore commandStore, TxnId txnId, Timestamp rootExecuteAt, @Nullable TokenKey byKey, int depth, boolean recurse)
|
||||
{
|
||||
return commandStore.chain(ExecutionContext.contextFor(txnId, "Populate txn_blocked_by"), safeStore -> {
|
||||
return commandStore.chain(ExecutionContext.unsequenced(txnId, "Populate txn_blocked_by"), safeStore -> {
|
||||
Command command = safeStore.unsafeGetNoCleanup(txnId).current();
|
||||
if (command == null || command.saveStatus() == SaveStatus.Uninitialised)
|
||||
return null;
|
||||
|
|
@ -231,7 +231,7 @@ public class DebugBlockedTxns
|
|||
|
||||
private AsyncChain<Void> visitKeysAsync(CommandStore commandStore, TokenKey key, Timestamp rootExecuteAt, int depth)
|
||||
{
|
||||
return commandStore.chain(ExecutionContext.contextFor(RoutingKeys.of(key.toUnseekable()), SYNC, READ_WRITE, "Populate txn_blocked_by"), safeStore -> {
|
||||
return commandStore.chain(ExecutionContext.unsequencedReadWrite(RoutingKeys.of(key.toUnseekable()), "Populate txn_blocked_by"), safeStore -> {
|
||||
visitKeysSync(safeStore, key, rootExecuteAt, depth);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,10 +40,10 @@ public class DebugExecution
|
|||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(DebugExecution.class);
|
||||
public static final boolean DEBUG_EXECUTION = CassandraRelevantProperties.ACCORD_DEBUG_EXECUTION.getBoolean(false);
|
||||
private static final long REPORT_MIN_LATENCY_MICROS = 20_000;
|
||||
private static final long REPORT_MIN_LATENCY_MICROS = 50_000;
|
||||
private static final long REPORT_CPU_RATIO = 2;
|
||||
private static final long REPORT_MAX_LATENCY_MICROS = 50_000;
|
||||
private static final long REPORT_CPU_MICROS = 10_000;
|
||||
private static final long REPORT_MAX_LATENCY_MICROS = 100_000;
|
||||
private static final long REPORT_CPU_MICROS = 50_000;
|
||||
|
||||
// TODO (expected): use sharded histogram so we can report global stats
|
||||
public static class DebugExecutor
|
||||
|
|
@ -94,43 +94,16 @@ public class DebugExecution
|
|||
long lockedForCpuMicros = (unlockedAtCpu - lockedAtCpu)/1000;
|
||||
if (lockedForMicros >= REPORT_MAX_LATENCY_MICROS)
|
||||
{
|
||||
report("Held lock for {}us (cpu:{}us)\n", lockedForMicros, lockedForCpuMicros);
|
||||
report("Held lock for {}us (cpu:{}us)", lockedForMicros, lockedForCpuMicros);
|
||||
}
|
||||
else if (lockedForMicros >= REPORT_MIN_LATENCY_MICROS && (lockedForMicros / lockedForCpuMicros) >= REPORT_CPU_RATIO)
|
||||
{
|
||||
report("Held lock for {}us with cpu time only {}us\n", lockedForMicros, lockedForCpuMicros);
|
||||
report("Held lock for {}us with cpu time only {}us", lockedForMicros, lockedForCpuMicros);
|
||||
}
|
||||
locked.increment(lockedForMicros);
|
||||
}
|
||||
}
|
||||
|
||||
public static class DebugExecutorLoop
|
||||
{
|
||||
final DebugExecutor owner;
|
||||
long lockAt;
|
||||
|
||||
public DebugExecutorLoop(DebugExecutor owner)
|
||||
{
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
public void onLock()
|
||||
{
|
||||
lockAt = nanoTime();
|
||||
}
|
||||
|
||||
public void onEnterLock()
|
||||
{
|
||||
owner.onEnterLock(lockAt);
|
||||
lockAt = 0;
|
||||
}
|
||||
|
||||
public void onExitLock()
|
||||
{
|
||||
owner.onExitLock();
|
||||
}
|
||||
}
|
||||
|
||||
public static class DebugExclusiveExecutor
|
||||
{
|
||||
public static DebugExclusiveExecutor maybeDebug(DebugExecutor owner, int commandStoreId)
|
||||
|
|
@ -200,7 +173,7 @@ public class DebugExecution
|
|||
|
||||
public List<Command> sanityCheck; // for AccordTask only
|
||||
long polledAt, preRunAt, runCompleteAt, completedAt;
|
||||
long releasedRangeScannerAt, releasedCommandsAt, releasedCommandsForKeyAt;
|
||||
long releasedRangeScannerAt, releasedStateAt;
|
||||
long runningAtCpu, runCompleteAtCpu;
|
||||
Thread thread;
|
||||
|
||||
|
|
@ -231,14 +204,9 @@ public class DebugExecution
|
|||
releasedRangeScannerAt = nanoTime();
|
||||
}
|
||||
|
||||
public void onReleasedCommands()
|
||||
public void onReleasedState()
|
||||
{
|
||||
releasedCommandsAt = nanoTime();
|
||||
}
|
||||
|
||||
public void onReleasedCommandsForKeys()
|
||||
{
|
||||
releasedCommandsForKeyAt = nanoTime();
|
||||
releasedStateAt = nanoTime();
|
||||
}
|
||||
|
||||
public void onCompleted(DebugExecutor owner)
|
||||
|
|
@ -254,9 +222,9 @@ public class DebugExecution
|
|||
runningMicros = (runCompleteAt - task.runningAt) / 1000;
|
||||
owner.running.increment(runningMicros);
|
||||
}
|
||||
long runToCleanMicros = (task.cleanupAt - runCompleteAt)/1000;
|
||||
long runToCleanMicros = (task.completeAt - runCompleteAt) / 1000;
|
||||
owner.runToCleanup.increment(runToCleanMicros);
|
||||
long cleanupMicros = (completedAt - task.cleanupAt)/1000;
|
||||
long cleanupMicros = (completedAt - task.completeAt) / 1000;
|
||||
owner.cleanup.increment(cleanupMicros);
|
||||
long totalMicros = (completedAt - polledAt)/1000;
|
||||
owner.taskTotal.increment(totalMicros);
|
||||
|
|
@ -266,7 +234,7 @@ public class DebugExecution
|
|||
String reason = "";
|
||||
if (totalMicros > REPORT_MAX_LATENCY_MICROS) reason += "LONG TIME ";
|
||||
if (totalCpu > REPORT_CPU_MICROS) reason += "HIGH CPU ";
|
||||
if ((totalMicros > REPORT_MIN_LATENCY_MICROS && (totalMicros/totalCpu) >= REPORT_CPU_RATIO)) reason += "LOW RATIO ";
|
||||
if ((totalMicros > REPORT_MIN_LATENCY_MICROS && (totalCpu == 0 || (totalMicros/totalCpu) >= REPORT_CPU_RATIO))) reason += "LOW RATIO ";
|
||||
report("{}{}: total {}us cpu:{}us ({}), pollToRun {}us, running {}us, runToClean {}us, cleanup {}us",
|
||||
reason, task, totalMicros, totalCpu, thread, pollToRunMicros, runningMicros, runToCleanMicros, cleanupMicros);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -221,7 +221,7 @@ public abstract class DebugTxnGraph<T, P>
|
|||
|
||||
private AsyncChain<TxnInfos<T>> submitRoot(CommandStore commandStore, TxnId txnId)
|
||||
{
|
||||
return commandStore.chain(ExecutionContext.contextFor(txnId, "Populate txn_graph"), safeStore -> {
|
||||
return commandStore.chain(ExecutionContext.unsequenced(txnId, "Populate txn_graph"), safeStore -> {
|
||||
Command command = safeStore.unsafeGetNoCleanup(txnId).current();
|
||||
if (command == null || command.saveStatus() == SaveStatus.Uninitialised)
|
||||
return AsyncChains.<TxnInfos<T>>success(null);
|
||||
|
|
@ -232,7 +232,7 @@ public abstract class DebugTxnGraph<T, P>
|
|||
private AsyncChain<TxnInfos<T>> submitParent(CommandStore commandStore, TxnId txnId, P param, Map<TxnId, SaveInfo> infos, Set<TxnId> visitedParent, int depth)
|
||||
{
|
||||
|
||||
return commandStore.chain(ExecutionContext.contextFor(txnId, "Populate txn_graph"), safeStore -> {
|
||||
return commandStore.chain(ExecutionContext.unsequenced(txnId, "Populate txn_graph"), safeStore -> {
|
||||
Command command = safeStore.unsafeGetNoCleanup(txnId).current();
|
||||
if (command == null || command.saveStatus() == SaveStatus.Uninitialised)
|
||||
return AsyncChains.<TxnInfos<T>>success(null);
|
||||
|
|
@ -344,7 +344,7 @@ public abstract class DebugTxnGraph<T, P>
|
|||
|
||||
private AsyncChain<Void> populateTxnAsync(CommandStore commandStore, TxnId txnId, Map<TxnId, SaveInfo> visited)
|
||||
{
|
||||
return commandStore.chain(ExecutionContext.contextFor(txnId, "Populate txn_graph"), safeStore -> {
|
||||
return commandStore.chain(ExecutionContext.unsequenced(txnId, "Populate txn_graph"), safeStore -> {
|
||||
Command command = safeStore.unsafeGetNoCleanup(txnId).current();
|
||||
visited.putIfAbsent(txnId, command == null || command.saveStatus() == SaveStatus.Uninitialised ? SaveInfo.NONE : new SaveInfo(command.saveStatus(), command.executeAtIfKnown()));
|
||||
});
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ public class JournalRangeIndex extends SemiSyncIntervalTree<Object[]> implements
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate(AccordCacheEntry<TxnId, Command> state)
|
||||
public void onUpdate(AccordCacheEntry<TxnId, Command, ?> state)
|
||||
{
|
||||
Summary summary = loader.ifRelevant(state);
|
||||
if (summary != null)
|
||||
|
|
@ -162,7 +162,7 @@ public class JournalRangeIndex extends SemiSyncIntervalTree<Object[]> implements
|
|||
if (isMaybeRelevant(i))
|
||||
{
|
||||
TxnId txnId = i.txnId;
|
||||
AccordCacheEntry<TxnId, Command> entry = c.getUnsafe(txnId);
|
||||
AccordCacheEntry<TxnId, Command, ?> entry = c.getUnsafe(txnId);
|
||||
Invariants.expect(entry != null, "%s found interval %s but no matching transaction in cache", owner.commandStore, i);
|
||||
if (entry != null)
|
||||
{
|
||||
|
|
@ -267,7 +267,7 @@ public class JournalRangeIndex extends SemiSyncIntervalTree<Object[]> implements
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate(AccordCacheEntry<TxnId, Command> state)
|
||||
public void onUpdate(AccordCacheEntry<TxnId, Command, ?> state)
|
||||
{
|
||||
TxnId txnId = state.key();
|
||||
if (txnId.is(Routable.Domain.Range))
|
||||
|
|
@ -334,7 +334,7 @@ public class JournalRangeIndex extends SemiSyncIntervalTree<Object[]> implements
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onEvict(AccordCacheEntry<TxnId, Command> state)
|
||||
public void onEvict(AccordCacheEntry<TxnId, Command, ?> state)
|
||||
{
|
||||
TxnId txnId = state.key();
|
||||
if (txnId.is(Routable.Domain.Range))
|
||||
|
|
|
|||
|
|
@ -22,12 +22,12 @@ import com.google.common.base.Throwables;
|
|||
|
||||
import org.assertj.core.api.Assertions;
|
||||
|
||||
import accord.api.RoutingKey;
|
||||
import accord.local.CommandStores;
|
||||
import accord.local.ExecutionContext;
|
||||
import accord.local.LoadKeys;
|
||||
import accord.local.Node;
|
||||
import accord.local.cfk.CommandsForKey;
|
||||
import accord.local.cfk.SafeCommandsForKey;
|
||||
import accord.primitives.Ranges;
|
||||
import accord.primitives.Routable;
|
||||
import accord.primitives.Txn;
|
||||
|
|
@ -43,7 +43,6 @@ import org.apache.cassandra.net.Verb;
|
|||
import org.apache.cassandra.schema.TableId;
|
||||
import org.apache.cassandra.service.accord.AccordCommandStore;
|
||||
import org.apache.cassandra.service.accord.AccordSafeCommandStore;
|
||||
import org.apache.cassandra.service.accord.AccordSafeCommandsForKey;
|
||||
import org.apache.cassandra.service.accord.AccordService;
|
||||
import org.apache.cassandra.service.accord.TokenRange;
|
||||
|
||||
|
|
@ -135,19 +134,16 @@ public class AccordDropTableBase extends TestBaseImpl
|
|||
TableId tableId = TableId.fromString(s);
|
||||
AccordService accord = (AccordService) AccordService.instance();
|
||||
TxnId syntheticTxnId = new TxnId(TxnId.MAX_EPOCH, 0, Txn.Kind.ExclusiveSyncPoint, Routable.Domain.Range, new Node.Id(1));
|
||||
ExecutionContext ctx = ExecutionContext.contextFor(syntheticTxnId, Ranges.single(TokenRange.fullRange(tableId, getPartitioner())), LoadKeys.SYNC, READ_WRITE, "Test");
|
||||
ExecutionContext ctx = ExecutionContext.unsequencedReadWrite(syntheticTxnId, Ranges.single(TokenRange.fullRange(tableId, getPartitioner())), "Test");
|
||||
CommandStores stores = accord.node().commandStores();
|
||||
for (int storeId : stores.ids())
|
||||
{
|
||||
AccordCommandStore store = (AccordCommandStore) stores.forId(storeId);
|
||||
getBlocking(store.chain(ctx, input -> {
|
||||
AccordSafeCommandStore safe = (AccordSafeCommandStore) input;
|
||||
for (RoutingKey key : safe.commandsForKeysKeys())
|
||||
for (SafeCommandsForKey safeCfk : safe.safeCommandsForKeys())
|
||||
{
|
||||
AccordSafeCommandsForKey safeCFK = (AccordSafeCommandsForKey) safe.ifLoadedAndInitialised(key);
|
||||
if (safeCFK == null) // we read and found a key, but its null at load time... so ignore it
|
||||
continue;
|
||||
CommandsForKey cfk = safeCFK.current();
|
||||
CommandsForKey cfk = safeCfk.current();
|
||||
CommandsForKey.TxnInfo minUndecided = cfk.minUndecidedManaged();
|
||||
if (minUndecided != null)
|
||||
throw new AssertionError("Undecided txn: " + minUndecided);
|
||||
|
|
|
|||
|
|
@ -355,6 +355,12 @@ public class AccordLoadTest extends AccordTestBase
|
|||
return builder.setArtificialLatencies(LATENCIES);
|
||||
}
|
||||
|
||||
private static SettingsBuilder populate(SettingsBuilder builder, int keyCount)
|
||||
{
|
||||
return builder.setKeySelector(roundrobin(keyCount))
|
||||
.setReadRatio(0f);
|
||||
}
|
||||
|
||||
private static SettingsBuilder ycsbA(SettingsBuilder builder, int keyCount)
|
||||
{
|
||||
return builder.setKeySelector(ycsbZipfian(keyCount))
|
||||
|
|
@ -832,7 +838,7 @@ public class AccordLoadTest extends AccordTestBase
|
|||
if (storeId.get() >= 0)
|
||||
{
|
||||
CommandStore commandStore = service.node().commandStores().forId(storeId.get());
|
||||
List<List<String>> result = AccordService.getBlocking(commandStore.submit(ExecutionContext.contextFor(candidate, "LoadTest"), safeStore -> {
|
||||
List<List<String>> result = AccordService.getBlocking(commandStore.submit(ExecutionContext.unsequenced(candidate, "LoadTest"), safeStore -> {
|
||||
SafeCommand safeCommand = safeStore.unsafeGet(candidate);
|
||||
PartialDeps deps = safeCommand.current().partialDeps();
|
||||
if (deps == null)
|
||||
|
|
@ -855,7 +861,7 @@ public class AccordLoadTest extends AccordTestBase
|
|||
for (List<String> info : result)
|
||||
{
|
||||
TxnId txnId = TxnId.parse(info.get(0));
|
||||
AccordService.getBlocking(commandStore.execute(ExecutionContext.contextFor(txnId, "LoadTest"), safeStore -> {
|
||||
AccordService.getBlocking(commandStore.execute(ExecutionContext.unsequenced(txnId, "LoadTest"), safeStore -> {
|
||||
SafeCommand safeCommand = safeStore.unsafeGet(txnId);
|
||||
if (safeCommand.current().executeAt != null)
|
||||
info.add(safeCommand.current().executeAt.toString());
|
||||
|
|
@ -915,7 +921,7 @@ public class AccordLoadTest extends AccordTestBase
|
|||
cluster.forEach(() -> {
|
||||
refresh(AccordExecutorMetrics.INSTANCE.elapsedRunning);
|
||||
refresh(AccordExecutorMetrics.INSTANCE.elapsed);
|
||||
System.out.printf("%tT.%tL (%d %d %d %d %d %d)ms (%d %d %d %d %d %d)ms (%d %d %d %d %.0f, %d %d %d)us %d %d %d\n", nowMillis, nowMillis,
|
||||
System.out.printf("%tT.%tL (%d %d %d %d %d %d)ms (%d %d %d %d %d %d)ms (%d %d %d)us %d %.0f (%d %d %d)us %d %d %d\n", nowMillis, nowMillis,
|
||||
getLatency(AccordCoordinatorMetrics.readMetrics.preacceptLatency, 0.5),
|
||||
getLatency(AccordCoordinatorMetrics.readMetrics.executeLatency, 0.5),
|
||||
getLatency(AccordCoordinatorMetrics.readMetrics.applyLatency, 0.5),
|
||||
|
|
@ -1092,13 +1098,13 @@ public class AccordLoadTest extends AccordTestBase
|
|||
try
|
||||
{
|
||||
test.setup();
|
||||
test.testLoad(withArtificialLatencies(ycsbA(new SettingsBuilder(), 100_000)
|
||||
test.testLoad(populate(new SettingsBuilder(), 1_000_000)
|
||||
// .setRatePerSecond(400).setMinRatePerSecond(200)
|
||||
// .setRatePerSecond(800).setMinRatePerSecond(200)
|
||||
.setRatePerSecond(1600).setMinRatePerSecond(200)
|
||||
.setIncreaseRatePerSecondInterval(5000)
|
||||
// .setTraceLast(5000)
|
||||
).build());
|
||||
.build());
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ import org.apache.cassandra.schema.Schema;
|
|||
import org.apache.cassandra.schema.TableId;
|
||||
import org.apache.cassandra.service.accord.AccordCacheEntry;
|
||||
import org.apache.cassandra.service.accord.AccordCommandStore;
|
||||
import org.apache.cassandra.service.accord.AccordSafeCommand;
|
||||
import org.apache.cassandra.service.accord.AccordService;
|
||||
import org.apache.cassandra.service.accord.api.PartitionKey;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
|
|
@ -86,7 +87,7 @@ public class AccordJournalConsistentExpungeTest extends TestBaseImpl
|
|||
Node node = service.node();
|
||||
AccordCommandStore commandStore = (AccordCommandStore) node.commandStores().unsafeForKey(key.toUnseekable());
|
||||
|
||||
Iterator<AccordCacheEntry<TxnId, Command>> iterator = commandStore.cachesUnsafe().commands().iterator();
|
||||
Iterator<AccordCacheEntry<TxnId, Command, AccordSafeCommand>> iterator = commandStore.cachesUnsafe().commands().iterator();
|
||||
|
||||
TxnId txnId = TxnId.NONE;
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,329 @@
|
|||
/*
|
||||
* 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.simulator.test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import accord.api.AsyncExecutor;
|
||||
import accord.api.ExclusiveAsyncExecutor;
|
||||
import accord.utils.async.Cancellable;
|
||||
|
||||
import org.apache.cassandra.concurrent.ExecutorPlus;
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.distributed.api.IIsolatedExecutor.SerializableSupplier;
|
||||
import org.apache.cassandra.service.accord.AccordExecutor;
|
||||
import org.apache.cassandra.service.accord.AccordExecutorAsyncSubmit;
|
||||
import org.apache.cassandra.service.accord.AccordExecutorSignalLoop;
|
||||
import org.apache.cassandra.service.accord.api.AccordAgent;
|
||||
import org.apache.cassandra.utils.concurrent.AsyncPromise;
|
||||
import org.apache.cassandra.utils.concurrent.SignalLock;
|
||||
|
||||
import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory;
|
||||
import static org.apache.cassandra.service.accord.AccordExecutor.Mode.RUN_WITHOUT_LOCK;
|
||||
|
||||
// TODO (required): have simulator intercept ReentantLock so we can test SyncSubmit and SemiSyncSubmit
|
||||
public class AccordExecutorAndCacheTest extends SimulationTestBase
|
||||
{
|
||||
@Test
|
||||
public void signalLoopTest()
|
||||
{
|
||||
executorTest(() -> new AccordExecutorSignalLoop(1, RUN_WITHOUT_LOCK, SignalLock.MAX_THREADS, -1, -1, TimeUnit.MICROSECONDS, i -> "Loop" + i, new AccordAgent()),
|
||||
16);
|
||||
}
|
||||
|
||||
static class Submitted
|
||||
{
|
||||
final AtomicInteger nextId = new AtomicInteger();
|
||||
final AtomicInteger doneBefore = new AtomicInteger();
|
||||
final ConcurrentHashMap<Integer, Collection<Future<?>>> consequences = new ConcurrentHashMap<>();
|
||||
|
||||
boolean isDone()
|
||||
{
|
||||
return isDoneBetween(0, nextId.get());
|
||||
}
|
||||
|
||||
boolean isDoneBefore(int before)
|
||||
{
|
||||
if (!isDoneBetween(doneBefore.get(), before))
|
||||
return false;
|
||||
doneBefore.accumulateAndGet(before, Integer::max);
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean isDoneBetween(int from, int before)
|
||||
{
|
||||
for (int id = from ; id < before ; ++id)
|
||||
{
|
||||
for (Future<?> future : consequences.get(id))
|
||||
{
|
||||
if (!future.isDone())
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Collection<Future<?>> start()
|
||||
{
|
||||
ConcurrentLinkedQueue<Future<?>> result = new ConcurrentLinkedQueue<>();
|
||||
|
||||
while (true)
|
||||
{
|
||||
int id = nextId.get();
|
||||
Object prev = consequences.putIfAbsent(id, result);
|
||||
nextId.compareAndSet(id, id + 1);
|
||||
if (prev == null)
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class Control extends ConcurrentLinkedQueue<Cancellable>
|
||||
{
|
||||
final Submitted submitted;
|
||||
final AtomicInteger count = new AtomicInteger();
|
||||
final float cancelChance;
|
||||
float processChance;
|
||||
|
||||
Control(float cancelChance, Submitted submitted)
|
||||
{
|
||||
this(submitted, cancelChance, ThreadLocalRandom.current().nextFloat() * 0.5f);
|
||||
}
|
||||
|
||||
Control(Submitted submitted, float cancelChance, float processChance)
|
||||
{
|
||||
this.submitted = submitted;
|
||||
this.cancelChance = cancelChance;
|
||||
this.processChance = processChance;
|
||||
}
|
||||
|
||||
void submit(AsyncExecutor executor, Collection<Future<?>> consequences, Consumer<Collection<Future<?>>> run)
|
||||
{
|
||||
AsyncPromise<?> future = new AsyncPromise<>();
|
||||
Cancellable cancel = executor.chain(() -> run.accept(consequences)).begin((success, fail) -> {
|
||||
if (fail == null) future.trySuccess(null);
|
||||
else
|
||||
{
|
||||
future.tryFailure(fail);
|
||||
if (fail instanceof CancellationException)
|
||||
run.accept(submitted.start());
|
||||
}
|
||||
});
|
||||
consequences.add(future);
|
||||
|
||||
if (cancel != null && ThreadLocalRandom.current().nextFloat() <= cancelChance)
|
||||
{
|
||||
add(cancel);
|
||||
count.incrementAndGet();
|
||||
}
|
||||
|
||||
if (count.get() > 0 && ThreadLocalRandom.current().nextFloat() <= processChance)
|
||||
{
|
||||
int cancelCount = 0;
|
||||
do
|
||||
{
|
||||
++cancelCount;
|
||||
|
||||
float delta = ThreadLocalRandom.current().nextFloat() - 0.5f;
|
||||
if (delta < 0) processChance /= delta;
|
||||
else processChance *= -delta;
|
||||
if (processChance < 0.001f || processChance > 0.999f)
|
||||
processChance = cancelChance;
|
||||
} while (count.decrementAndGet() > 0 && ThreadLocalRandom.current().nextFloat() <= processChance);
|
||||
|
||||
// do outside of loop to avoid reentry
|
||||
while (cancelCount-- > 0)
|
||||
remove().cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void executorTest(SerializableSupplier<AccordExecutor> supplier, int submissionThreads)
|
||||
{
|
||||
simulate(arr(() -> {
|
||||
try
|
||||
{
|
||||
DatabaseDescriptor.daemonInitialization();
|
||||
ExecutorPlus submit = executorFactory().pooled("submit-test", submissionThreads);
|
||||
AccordExecutor executor = supplier.get();
|
||||
Lock lock = executor.unsafeLock();
|
||||
ExclusiveAsyncExecutor sequentialExecutor = executor.newExclusiveExecutor();
|
||||
Executor lockExecutor = executorFactory().sequential("lock");
|
||||
|
||||
for (float sleepChance : new float[] { 0f, 0.01f, 0.1f })
|
||||
{
|
||||
for (float lockChance : new float[] { 0f, 0.01f, 0.1f })
|
||||
{
|
||||
for (float cancelChance : new float[] { 0f, 0.01f, 0.1f })
|
||||
{
|
||||
System.out.println(String.format("sleepChance %.2f, lockChance %.2f, cancelChance %.2f", sleepChance, lockChance, cancelChance));
|
||||
List<Future<?>> done = new ArrayList<>();
|
||||
Submitted submitted = new Submitted();
|
||||
for (int i = 0; i < submissionThreads; ++i)
|
||||
{
|
||||
int id = i;
|
||||
done.add(submit.submit(() -> {
|
||||
try
|
||||
{
|
||||
submitLoop(id, lock, executor, sequentialExecutor, lockExecutor, 20, 10, sleepChance, lockChance, new Control(cancelChance, submitted), submitted);
|
||||
}
|
||||
catch (ExecutionException | InterruptedException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}));
|
||||
}
|
||||
for (Future<?> f : done)
|
||||
f.get();
|
||||
|
||||
if (!submitted.isDone())
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
throw new RuntimeException(t);
|
||||
}
|
||||
}),
|
||||
() -> {}, 1L);
|
||||
}
|
||||
|
||||
private static void submitLoop(int id, Lock lock, AccordExecutor executor, ExclusiveAsyncExecutor sequentialExecutor, Executor lockExecutor, int outerLoop, int innerLoop, float sleepChance, float lockChance, Control control, Submitted submitted) throws ExecutionException, InterruptedException
|
||||
{
|
||||
ConcurrentLinkedQueue<Future<?>> awaitConsequences = new ConcurrentLinkedQueue<>();
|
||||
while (outerLoop-- > 0)
|
||||
{
|
||||
List<Collection<Future<?>>> allAwaitSubmitted = new ArrayList<>();
|
||||
for (int i = 0; i < innerLoop; ++i)
|
||||
{
|
||||
Collection<Future<?>> awaitSubmitted = submitted.start();
|
||||
allAwaitSubmitted.add(awaitSubmitted);
|
||||
submitRecursive(lock, executor, sequentialExecutor, 1 + i, awaitSubmitted, awaitConsequences, submitted, sleepChance, lockChance, control);
|
||||
}
|
||||
|
||||
AtomicBoolean done = new AtomicBoolean();
|
||||
submitUntil(lock, lockExecutor, sleepChance, done::get);
|
||||
for (Collection<Future<?>> awaitSubmitted : allAwaitSubmitted)
|
||||
await(awaitSubmitted, CancellationException.class);
|
||||
await(awaitConsequences, null);
|
||||
done.set(true);
|
||||
System.out.println("Loop " + id + '.' + (1 + outerLoop));
|
||||
}
|
||||
}
|
||||
|
||||
private static void submitRecursive(Lock lock, AccordExecutor executor, ExclusiveAsyncExecutor sequentialExecutor, int count, Collection<Future<?>> consequences, Collection<Future<?>> awaitConsequences, Submitted submitted, float sleepChance, float lockChance, Control control)
|
||||
{
|
||||
AsyncExecutor submitTo = ThreadLocalRandom.current().nextBoolean() ? executor : sequentialExecutor;
|
||||
|
||||
control.submit(submitTo, consequences, nextConsequences -> {
|
||||
ThreadLocalRandom rnd = ThreadLocalRandom.current();
|
||||
boolean locked = false;
|
||||
if (rnd.nextFloat() < lockChance)
|
||||
{
|
||||
if (rnd.nextBoolean()) locked = lock.tryLock();
|
||||
else { locked = true; lock.lock(); }
|
||||
}
|
||||
if (ThreadLocalRandom.current().nextFloat() < 0.01f)
|
||||
{
|
||||
int expectDoneBefore = submitted.nextId.get();
|
||||
AsyncPromise<Void> afterConsequences = new AsyncPromise<>();
|
||||
executor.afterSubmittedAndConsequences(() -> {
|
||||
if (!submitted.isDoneBefore(expectDoneBefore))
|
||||
throw new AssertionError();
|
||||
afterConsequences.setSuccess(null);
|
||||
});
|
||||
awaitConsequences.add(afterConsequences);
|
||||
}
|
||||
try
|
||||
{
|
||||
if (count > 1)
|
||||
submitRecursive(lock, executor, sequentialExecutor, count -1, nextConsequences, awaitConsequences, submitted, sleepChance, lockChance, control);
|
||||
if (rnd.nextFloat() < sleepChance)
|
||||
LockSupport.parkNanos(rnd.nextInt(10000, 100000));
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (locked)
|
||||
lock.unlock();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void submitUntil(Lock lock, Executor executor, float sleepChance, BooleanSupplier done)
|
||||
{
|
||||
if (done.getAsBoolean())
|
||||
return;
|
||||
|
||||
executor.execute(() -> {
|
||||
|
||||
ThreadLocalRandom rnd = ThreadLocalRandom.current();
|
||||
boolean tryLock = rnd.nextBoolean();
|
||||
boolean locked = !tryLock;
|
||||
if (tryLock) locked = lock.tryLock();
|
||||
else lock.lock();
|
||||
try
|
||||
{
|
||||
if (rnd.nextFloat() < sleepChance)
|
||||
LockSupport.parkNanos(rnd.nextInt(10000, 100000));
|
||||
|
||||
submitUntil(lock, executor, sleepChance, done);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (locked)
|
||||
lock.unlock();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void await(Collection<Future<?>> await, @Nullable Class<? extends Throwable> ignore) throws InterruptedException, ExecutionException
|
||||
{
|
||||
for (Future<?> future : await)
|
||||
{
|
||||
try { future.get(); }
|
||||
catch (ExecutionException e)
|
||||
{
|
||||
if (ignore == null || !(ignore.isInstance(e.getCause())))
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -91,6 +91,7 @@ import org.apache.cassandra.service.accord.api.TokenKey;
|
|||
import org.apache.cassandra.utils.FBUtilities;
|
||||
|
||||
import static accord.local.ExecutionContext.contextFor;
|
||||
import static accord.local.ExecutionContext.unsequencedReadWrite;
|
||||
import static accord.local.LoadKeys.SYNC;
|
||||
import static accord.local.LoadKeysFor.READ_WRITE;
|
||||
import static accord.local.RedundantStatus.SomeStatus.GC_BEFORE_AND_LOCALLY_DURABLE;
|
||||
|
|
@ -322,28 +323,28 @@ public class CompactionAccordIteratorsTest
|
|||
PartialDeps partialDeps = Deps.NONE.intersecting(AccordTestUtils.fullRange(txn));
|
||||
PartialTxn partialTxn = txn.slice(commandStore.unsafeGetRangesForEpoch().currentRanges(), true);
|
||||
Route<?> partialRoute = route.overlapping(commandStore.unsafeGetRangesForEpoch().currentRanges());
|
||||
getBlocking(commandStore.execute(contextFor(txnId, route, SYNC, READ_WRITE, "Test"), safe -> {
|
||||
getBlocking(commandStore.execute(unsequencedReadWrite(txnId, route, "Test"), safe -> {
|
||||
CheckedCommands.preaccept(safe, txnId, partialTxn, route, (a, b) -> {});
|
||||
}));
|
||||
flush(commandStore);
|
||||
getBlocking(commandStore.execute(contextFor(txnId, route, SYNC, READ_WRITE, "Test"), safe -> {
|
||||
getBlocking(commandStore.execute(unsequencedReadWrite(txnId, route, "Test"), safe -> {
|
||||
CheckedCommands.accept(safe, txnId, Ballot.ZERO, partialRoute, txnId, partialDeps, (a, b) -> {});
|
||||
}));
|
||||
flush(commandStore);
|
||||
getBlocking(commandStore.execute(contextFor(txnId, route, SYNC, READ_WRITE, "Test"), safe -> {
|
||||
getBlocking(commandStore.execute(unsequencedReadWrite(txnId, route, "Test"), safe -> {
|
||||
CheckedCommands.commit(safe, SaveStatus.Stable, Ballot.ZERO, txnId, route, partialTxn, txnId, partialDeps, (a, b) -> {});
|
||||
}));
|
||||
flush(commandStore);
|
||||
getBlocking(commandStore.chain(contextFor(txnId, route, SYNC, READ_WRITE, "Test"), safe -> {
|
||||
getBlocking(commandStore.chain(unsequencedReadWrite(txnId, route, "Test"), safe -> {
|
||||
return AccordTestUtils.processTxnResultDirect(safe, txnId, partialTxn, txnId);
|
||||
}).flatMap(i -> i).flatMap(result -> commandStore.chain(contextFor(txnId, route, SYNC, READ_WRITE, "Test"), safe -> {
|
||||
}).flatMap(i -> i).flatMap(result -> commandStore.chain(unsequencedReadWrite(txnId, route, "Test"), safe -> {
|
||||
CheckedCommands.apply(safe, txnId, route, txnId, partialDeps, partialTxn, result.left, result.right, (a, b) -> {});
|
||||
})));
|
||||
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
|
||||
spinAssertEquals(true, 5, () -> {
|
||||
return getBlocking(commandStore.submit(contextFor(txnId, route, SYNC, READ_WRITE, "Test"), safe -> {
|
||||
return getBlocking(commandStore.submit(unsequencedReadWrite(txnId, route, "Test"), safe -> {
|
||||
StoreParticipants participants = StoreParticipants.all(route);
|
||||
Command command = safe.get(txnId, participants).current();
|
||||
return command.hasBeen(Status.Applied);
|
||||
|
|
|
|||
|
|
@ -20,14 +20,24 @@ package org.apache.cassandra.service.accord;
|
|||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import accord.local.SafeState;
|
||||
|
||||
import org.apache.cassandra.service.accord.AccordCache.Type;
|
||||
import org.apache.cassandra.service.accord.AccordCacheEntry.LockMode;
|
||||
import org.apache.cassandra.service.accord.AccordCacheEntry.Status;
|
||||
|
||||
public class AccordCacheEntryTest
|
||||
{
|
||||
static class CacheEntry extends AccordCacheEntry<String, String>
|
||||
static class TestSafeState extends SafeState<String> implements AccordSafeState<String, String, TestSafeState>
|
||||
{
|
||||
public CacheEntry(String key, Type<String, String, ?>.Instance instance)
|
||||
@Override public AccordCacheEntry<String, String, TestSafeState> global() { return null; }
|
||||
@Override public void preExecute(AccordTask<?> owner, LockMode lockMode) {}
|
||||
@Override public void postExecute(AccordTask<?> owner) {}
|
||||
}
|
||||
|
||||
static class CacheEntry extends AccordCacheEntry<String, String, TestSafeState>
|
||||
{
|
||||
public CacheEntry(String key, Type<String, String, TestSafeState>.Instance instance)
|
||||
{
|
||||
super(key, instance);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,13 +23,18 @@ import org.agrona.concurrent.NoOpLock;
|
|||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import accord.local.ExecutionContext;
|
||||
import accord.local.SafeState;
|
||||
|
||||
import org.apache.cassandra.cache.CacheSize;
|
||||
import org.apache.cassandra.concurrent.ExecutorPlus;
|
||||
import org.apache.cassandra.concurrent.ManualExecutor;
|
||||
import org.apache.cassandra.metrics.AccordCacheMetrics;
|
||||
import org.apache.cassandra.service.accord.AccordCacheEntry.LockMode;
|
||||
import org.apache.cassandra.service.accord.AccordCacheEntry.SaveExecutor;
|
||||
import org.apache.cassandra.service.accord.AccordCacheEntry.Status;
|
||||
|
||||
import static org.apache.cassandra.service.accord.AccordCacheEntry.LockMode.RELEASE_QUEUE;
|
||||
import static org.apache.cassandra.service.accord.AccordTestUtils.testLoad;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
|
|
@ -40,85 +45,56 @@ public class AccordCacheTest
|
|||
{
|
||||
private static final long DEFAULT_NODE_SIZE = nodeSize(0);
|
||||
|
||||
private static abstract class TestSafeState<T> implements AccordSafeState<T, T>
|
||||
private static abstract class TestSafeState<T, S extends SafeState<T> & AccordSafeState<T, T, S>> extends SafeState<T> implements AccordSafeState<T, T, S>
|
||||
{
|
||||
protected boolean invalidated = false;
|
||||
protected final AccordCacheEntry<T, T> global;
|
||||
private T original = null;
|
||||
protected final AccordCacheEntry<T, T, S> global;
|
||||
|
||||
public TestSafeState(AccordCacheEntry<T, T> global)
|
||||
public TestSafeState(AccordCacheEntry<T, T, S> global)
|
||||
{
|
||||
this.global = global;
|
||||
}
|
||||
|
||||
public AccordCacheEntry<T, T> global()
|
||||
public AccordCacheEntry<T, T, S> global()
|
||||
{
|
||||
return global;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T key()
|
||||
{
|
||||
return global.key();
|
||||
}
|
||||
public final T key() { return global.key(); }
|
||||
|
||||
@Override
|
||||
public T current()
|
||||
public void preExecute(AccordTask<?> owner, LockMode lockMode)
|
||||
{
|
||||
return global.getExclusive();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(T update)
|
||||
{
|
||||
global.setExclusive(update);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T original()
|
||||
{
|
||||
return original;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preExecute()
|
||||
{
|
||||
original = global.getExclusive();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Throwable failure()
|
||||
{
|
||||
return global.failure();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markUnsafe()
|
||||
{
|
||||
invalidated = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUnsafe()
|
||||
{
|
||||
return invalidated;
|
||||
requireUninitialised();
|
||||
current = global.lockExclusive(owner, lockMode);
|
||||
setSafe();
|
||||
}
|
||||
}
|
||||
|
||||
private static class SafeString extends TestSafeState<String>
|
||||
private static class SafeString extends TestSafeState<String, SafeString>
|
||||
{
|
||||
public SafeString(AccordCacheEntry<String, String> global)
|
||||
public SafeString(AccordCacheEntry<String, String, SafeString> global)
|
||||
{
|
||||
super(global);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postExecute(AccordTask<?> owner)
|
||||
{
|
||||
global.releaseExclusive(this, owner);
|
||||
}
|
||||
}
|
||||
|
||||
private static class SafeInt extends TestSafeState<Integer>
|
||||
private static class SafeInt extends TestSafeState<Integer, SafeInt>
|
||||
{
|
||||
public SafeInt(AccordCacheEntry<Integer, Integer> global)
|
||||
public SafeInt(AccordCacheEntry<Integer, Integer, SafeInt> global)
|
||||
{
|
||||
super(global);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postExecute(AccordTask<?> owner)
|
||||
{
|
||||
global.releaseExclusive(this, owner);
|
||||
}
|
||||
}
|
||||
|
||||
private static long emptyNodeSize()
|
||||
|
|
@ -264,6 +240,7 @@ public class AccordCacheTest
|
|||
assertCacheMetrics(cacheMetrics, 0, 3, 3, 3);
|
||||
|
||||
SafeString safeString = instance.acquire("1");
|
||||
safeString.preExecute(new AccordTask<>(null, (ExecutionContext.Empty)() -> "Test", null), RELEASE_QUEUE);
|
||||
Assert.assertEquals(Status.LOADED, safeString.global.status());
|
||||
|
||||
assertCacheState(cache, 1, 3, nodeSize(1) * 3);
|
||||
|
|
@ -392,6 +369,7 @@ public class AccordCacheTest
|
|||
assertCacheState(cache, 1, 1, nodeSize(1));
|
||||
|
||||
SafeString safeString2 = instance.acquire("0");
|
||||
safeString2.preExecute(new AccordTask<>(null, (ExecutionContext.Empty)() -> "Test", null), RELEASE_QUEUE);
|
||||
Assert.assertEquals("0", safeString2.current());
|
||||
Assert.assertEquals(Status.LOADED, safeString1.global.status());
|
||||
Assert.assertEquals(2, instance.references("0", SafeString.class));
|
||||
|
|
|
|||
|
|
@ -18,8 +18,6 @@
|
|||
|
||||
package org.apache.cassandra.service.accord;
|
||||
|
||||
import java.util.NavigableMap;
|
||||
import java.util.TreeMap;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import org.junit.Assert;
|
||||
|
|
@ -73,6 +71,7 @@ import org.apache.cassandra.utils.Pair;
|
|||
import static accord.primitives.Status.Durability.AllQuorums;
|
||||
import static com.google.common.collect.Iterables.getOnlyElement;
|
||||
import static org.apache.cassandra.cql3.statements.schema.CreateTableStatement.parse;
|
||||
import static org.apache.cassandra.service.accord.AccordCacheEntry.LockMode.RELEASE_QUEUE;
|
||||
import static org.apache.cassandra.service.accord.AccordService.getBlocking;
|
||||
import static org.apache.cassandra.service.accord.AccordTestUtils.Commands.preaccepted;
|
||||
import static org.apache.cassandra.service.accord.AccordTestUtils.ballot;
|
||||
|
|
@ -140,6 +139,7 @@ public class AccordCommandStoreTest
|
|||
promised, executeAt, txn, dependencies, accepted,
|
||||
waitingOn, result.left, TxnDataResult.PERSISTABLE);
|
||||
AccordSafeCommand safeCommand = new AccordSafeCommand(loaded(txnId, null));
|
||||
safeCommand.preExecute(new AccordTask<>(null, ExecutionContext.unsequenced(txnId, "Test"), null), RELEASE_QUEUE);
|
||||
safeCommand.set(expected);
|
||||
// In practice we should never need to save it with the condition boolean set
|
||||
// Not sure why this test does that
|
||||
|
|
@ -168,10 +168,10 @@ public class AccordCommandStoreTest
|
|||
Command command2 = preaccepted(txnId2, txn, timestamp(1, clock.incrementAndGet(), 1));
|
||||
|
||||
AccordSafeCommandsForKey cfk = new AccordSafeCommandsForKey(loaded(key, null));
|
||||
cfk.initialize();
|
||||
cfk.preExecute(new AccordTask<>(null, ExecutionContext.unsequenced(txnId1, "Test"), null), RELEASE_QUEUE);
|
||||
|
||||
cfk.set(cfk.current().update(new TestSafeCommandStore(ExecutionContext.contextFor(command1.txnId(), "Test")), command1).cfk());
|
||||
cfk.set(cfk.current().update(new TestSafeCommandStore(ExecutionContext.contextFor(command1.txnId(), "Test")), command2).cfk());
|
||||
cfk.set(cfk.current().update(new TestSafeCommandStore(ExecutionContext.unsequenced(command1.txnId(), "Test")), command1).cfk());
|
||||
cfk.set(cfk.current().update(new TestSafeCommandStore(ExecutionContext.unsequenced(command1.txnId(), "Test")), command2).cfk());
|
||||
|
||||
CommandsForKeyAccessor.systemTableUpdater(commandStore.id(), (TokenKey)cfk.key(), cfk.current(), null, commandStore.nextSystemTimestampMicros()).run();
|
||||
logger.info("E: {}", cfk);
|
||||
|
|
@ -180,11 +180,4 @@ public class AccordCommandStoreTest
|
|||
|
||||
Assert.assertEquals(cfk.current(), actual);
|
||||
}
|
||||
|
||||
private static <K, V extends AccordSafeState<K, ?>> NavigableMap<K, V> toNavigableMap(V safeState)
|
||||
{
|
||||
TreeMap<K, V> map = new TreeMap<>();
|
||||
map.put(safeState.key(), safeState);
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -174,7 +174,7 @@ public class AccordCommandTest
|
|||
Commit commit = Commit.SerializerSupport.create(txnId, route, 1, 1, Commit.Kind.StableWithTxnAndDeps, Ballot.ZERO, executeAt, partialTxn, deps, fullRoute);
|
||||
getBlocking(commandStore.execute(commit, commit::apply));
|
||||
|
||||
getBlocking(commandStore.execute(ExecutionContext.contextFor(txnId, Keys.of(key).toParticipants(), LoadKeys.SYNC, READ_WRITE, "Test"), safeStore -> {
|
||||
getBlocking(commandStore.execute(ExecutionContext.unsequencedReadWrite(txnId, Keys.of(key).toParticipants(), "Test"), safeStore -> {
|
||||
Command before = safeStore.ifInitialised(txnId).current();
|
||||
Assert.assertEquals(commit.executeAt, before.executeAt());
|
||||
Assert.assertTrue(before.hasBeen(Status.Committed));
|
||||
|
|
|
|||
|
|
@ -86,6 +86,7 @@ import org.apache.cassandra.utils.FBUtilities;
|
|||
import org.apache.cassandra.utils.concurrent.Condition;
|
||||
|
||||
import static accord.local.ExecutionContext.contextFor;
|
||||
import static accord.local.ExecutionContext.unsequencedReadWrite;
|
||||
import static accord.local.LoadKeys.SYNC;
|
||||
import static accord.local.LoadKeysFor.READ_WRITE;
|
||||
import static accord.utils.Property.qt;
|
||||
|
|
@ -127,7 +128,7 @@ public class AccordTaskTest
|
|||
AccordCommandStore commandStore = createAccordCommandStore(clock::incrementAndGet, "ks", "tbl");
|
||||
TxnId txnId = txnId(1, clock.incrementAndGet(), 1);
|
||||
|
||||
getBlocking(commandStore.execute(ExecutionContext.contextFor(txnId, "Test"), instance -> {
|
||||
getBlocking(commandStore.execute(ExecutionContext.unsequenced(txnId, "Test"), instance -> {
|
||||
// TODO review: This change to `ifInitialized` was done in a lot of places and it doesn't preserve this property
|
||||
// I fixed this reference to point to `ifLoadedAndInitialised` and but didn't update other places
|
||||
Assert.assertNull(instance.ifInitialised(txnId));
|
||||
|
|
@ -141,7 +142,7 @@ public class AccordTaskTest
|
|||
AccordCommandStore commandStore = createAccordCommandStore(clock::incrementAndGet, "ks", "tbl");
|
||||
TxnId txnId = txnId(1, clock.incrementAndGet(), 1);
|
||||
|
||||
getBlocking(commandStore.execute(ExecutionContext.contextFor(txnId, "Test"), safe -> {
|
||||
getBlocking(commandStore.execute(ExecutionContext.unsequenced(txnId, "Test"), safe -> {
|
||||
StoreParticipants participants = StoreParticipants.empty(txnId);
|
||||
SafeCommand command = safe.get(txnId, participants);
|
||||
Assert.assertNotNull(command);
|
||||
|
|
@ -198,7 +199,7 @@ public class AccordTaskTest
|
|||
route.overlapping(ranges);
|
||||
PartialDeps deps = PartialDeps.builder(ranges, true).build();
|
||||
|
||||
Command command = getBlocking(commandStore.submit(contextFor(txnId, route, SYNC, READ_WRITE, "Test"), safe -> {
|
||||
Command command = getBlocking(commandStore.submit(unsequencedReadWrite(txnId, route, "Test"), safe -> {
|
||||
CheckedCommands.preaccept(safe, txnId, partialTxn, route, appendDiffToLog(commandStore));
|
||||
CheckedCommands.commit(safe, SaveStatus.Stable, Ballot.ZERO, txnId, route, partialTxn, executeAt, deps, appendDiffToLog(commandStore));
|
||||
return safe.ifInitialised(txnId).current();
|
||||
|
|
@ -246,7 +247,7 @@ public class AccordTaskTest
|
|||
Route<?> partialRoute = route.overlapping(ranges);
|
||||
PartialDeps deps = PartialDeps.builder(ranges, true).build();
|
||||
|
||||
Command command = getBlocking(commandStore.submit(contextFor(txnId, route, SYNC, READ_WRITE, "Test"), safe -> {
|
||||
Command command = getBlocking(commandStore.submit(unsequencedReadWrite(txnId, route, "Test"), safe -> {
|
||||
CheckedCommands.preaccept(safe, txnId, partialTxn, route, appendDiffToLog(commandStore));
|
||||
CheckedCommands.accept(safe, txnId, Ballot.ZERO, partialRoute, executeAt, deps, appendDiffToLog(commandStore));
|
||||
CheckedCommands.commit(safe, SaveStatus.Committed, Ballot.ZERO, txnId, route, partialTxn, executeAt, deps, appendDiffToLog(commandStore));
|
||||
|
|
@ -441,7 +442,7 @@ public class AccordTaskTest
|
|||
AssertionError error = null;
|
||||
for (T key : keys)
|
||||
{
|
||||
AccordCacheEntry<T, ?> node = cache.getUnsafe(key);
|
||||
AccordCacheEntry<T, ?, ?> node = cache.getUnsafe(key);
|
||||
if (node == null) continue;
|
||||
try
|
||||
{
|
||||
|
|
@ -476,7 +477,7 @@ public class AccordTaskTest
|
|||
{
|
||||
for (T key : keys)
|
||||
{
|
||||
AccordCacheEntry<T, ?> node = cache.getUnsafe(key);
|
||||
AccordCacheEntry<T, ?, ?> node = cache.getUnsafe(key);
|
||||
if (node == null) continue;
|
||||
Awaitility.await("For node " + node.key() + " to complete")
|
||||
.atMost(Duration.ofMinutes(1))
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ import accord.local.Node;
|
|||
import accord.local.Node.Id;
|
||||
import accord.local.NodeCommandStoreService;
|
||||
import accord.local.SafeCommandStore;
|
||||
import accord.local.SafeState;
|
||||
import accord.local.StoreParticipants;
|
||||
import accord.local.TimeService;
|
||||
import accord.local.durability.DurabilityService;
|
||||
|
|
@ -82,7 +83,6 @@ import accord.topology.TopologyManager;
|
|||
import accord.utils.SortedArrays.SortedArrayList;
|
||||
import accord.utils.async.AsyncChain;
|
||||
import accord.utils.async.AsyncChains;
|
||||
import accord.utils.async.Cancellable;
|
||||
|
||||
import org.apache.cassandra.ServerTestUtils;
|
||||
import org.apache.cassandra.concurrent.ExecutorPlus;
|
||||
|
|
@ -104,6 +104,7 @@ import org.apache.cassandra.schema.TableId;
|
|||
import org.apache.cassandra.schema.TableMetadata;
|
||||
import org.apache.cassandra.service.ClientState;
|
||||
import org.apache.cassandra.service.accord.AccordCacheEntry.LoadExecutor;
|
||||
import org.apache.cassandra.service.accord.AccordExecutor.IOTask;
|
||||
import org.apache.cassandra.service.accord.api.AccordAgent;
|
||||
import org.apache.cassandra.service.accord.api.PartitionKey;
|
||||
import org.apache.cassandra.service.accord.journal.AccordJournal;
|
||||
|
|
@ -114,7 +115,6 @@ import org.apache.cassandra.service.accord.txn.TxnQuery;
|
|||
import org.apache.cassandra.service.accord.txn.TxnRead;
|
||||
import org.apache.cassandra.utils.Pair;
|
||||
import org.apache.cassandra.utils.concurrent.Condition;
|
||||
import org.apache.cassandra.utils.concurrent.Future;
|
||||
import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException;
|
||||
|
||||
import static accord.primitives.Routable.Domain.Key;
|
||||
|
|
@ -123,6 +123,7 @@ import static accord.primitives.SaveStatus.PreAccepted;
|
|||
import static accord.primitives.Status.Durability.NotDurable;
|
||||
import static accord.primitives.Txn.Kind.Write;
|
||||
import static java.lang.String.format;
|
||||
import static org.apache.cassandra.service.accord.AccordCacheEntry.LockMode.RELEASE_QUEUE;
|
||||
import static org.apache.cassandra.service.accord.AccordExecutor.Mode.RUN_WITH_LOCK;
|
||||
import static org.apache.cassandra.service.accord.AccordService.getBlocking;
|
||||
|
||||
|
|
@ -162,16 +163,16 @@ public class AccordTestUtils
|
|||
}
|
||||
}
|
||||
|
||||
public static <K, V> AccordCacheEntry<K, V> loaded(K key, V value)
|
||||
public static <K, V, S extends SafeState<V> & AccordSafeState<K, V, S>> AccordCacheEntry<K, V, S> loaded(K key, V value)
|
||||
{
|
||||
AccordCacheEntry<K, V> global = new AccordCacheEntry<>(key, null);
|
||||
AccordCacheEntry<K, V, S> global = new AccordCacheEntry<>(key, null);
|
||||
global.initialize(value);
|
||||
return global;
|
||||
}
|
||||
|
||||
public static AccordSafeCommand safeCommand(Command command)
|
||||
{
|
||||
AccordCacheEntry<TxnId, Command> global = loaded(command.txnId(), command);
|
||||
AccordCacheEntry<TxnId, Command, AccordSafeCommand> global = loaded(command.txnId(), command);
|
||||
return new AccordSafeCommand(global);
|
||||
}
|
||||
|
||||
|
|
@ -188,9 +189,9 @@ public class AccordTestUtils
|
|||
return new LoadExecutor<>()
|
||||
{
|
||||
@Override
|
||||
public <K, V> Cancellable load(P1 p1, P2 p2, AccordCacheEntry<K, V> entry)
|
||||
public <K, V> IOTask load(P1 p1, P2 p2, AccordCacheEntry<K, V, ?> entry)
|
||||
{
|
||||
Future<?> future = executor.submit(() -> {
|
||||
executor.submit(() -> {
|
||||
V v;
|
||||
try { v = entry.owner.parent().adapter().load(entry.owner.commandStore, entry.key()); }
|
||||
catch (Throwable t)
|
||||
|
|
@ -200,19 +201,19 @@ public class AccordTestUtils
|
|||
}
|
||||
entry.loaded(v);
|
||||
});
|
||||
return () -> future.cancel(true);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static <K, V> void testLoad(ManualExecutor executor, AccordSafeState<K, V> safeState, V val)
|
||||
public static <K, V, S extends SafeState<V> & AccordSafeState<K, V, S>> void testLoad(ManualExecutor executor, S safeState, V val)
|
||||
{
|
||||
Assert.assertEquals(AccordCacheEntry.Status.WAITING_TO_LOAD, safeState.global().status());
|
||||
safeState.global().load(loadExecutor(executor), null, null);
|
||||
Assert.assertEquals(AccordCacheEntry.Status.LOADING, safeState.global().status());
|
||||
executor.runOne();
|
||||
Assert.assertEquals(AccordCacheEntry.Status.LOADED, safeState.global().status());
|
||||
safeState.preExecute();
|
||||
safeState.preExecute(new AccordTask<>(null, (ExecutionContext.Empty)() -> "Test", null), RELEASE_QUEUE);
|
||||
Assert.assertEquals(val, safeState.current());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ public class SimpleSimulatedAccordCommandStoreTest extends SimulatedAccordComman
|
|||
for (int i = 0, examples = 100; i < examples; i++)
|
||||
{
|
||||
TxnId id = AccordGens.txnIds().next(rs);
|
||||
instance.process(ExecutionContext.contextFor(id, "Test"), (safe) -> {
|
||||
instance.process(ExecutionContext.unsequenced(id, "Test"), (safe) -> {
|
||||
var safeCommand = safe.get(id, StoreParticipants.empty(id));
|
||||
var command = safeCommand.current();
|
||||
Assertions.assertThat(command.saveStatus()).isEqualTo(SaveStatus.Uninitialised);
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import java.util.concurrent.locks.LockSupport;
|
|||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.LongSupplier;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
|
|
@ -180,7 +181,10 @@ public class SimulatedAccordTaskTest extends SimulatedAccordCommandStoreTestBase
|
|||
|
||||
private static AccordTask<Void> operation(SimulatedAccordCommandStore instance, ExecutionContext ctx, Action action, BooleanSupplier delay)
|
||||
{
|
||||
return new SimulatedOperation(instance.commandStore, ctx, action == Action.FAILURE ? SimulatedOperation.Action.FAILURE : SimulatedOperation.Action.SUCCESS);
|
||||
|
||||
Function<SafeCommandStore, Void> function = action == Action.FAILURE ? safeStore -> { throw new SimulatedFault("Operation failed for keys " + ctx.keys()); }
|
||||
: safeStore -> null;
|
||||
return AccordTask.create(instance.commandStore, ctx, function);
|
||||
}
|
||||
|
||||
private static class Counter implements BiConsumer<Object, Throwable>
|
||||
|
|
@ -196,26 +200,6 @@ public class SimulatedAccordTaskTest extends SimulatedAccordCommandStoreTestBase
|
|||
}
|
||||
}
|
||||
|
||||
private static class SimulatedOperation extends AccordTask<Void>
|
||||
{
|
||||
enum Action { SUCCESS, FAILURE}
|
||||
private final Action action;
|
||||
|
||||
public SimulatedOperation(AccordCommandStore commandStore, ExecutionContext executionContext, Action action)
|
||||
{
|
||||
super(commandStore, executionContext);
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void apply(SafeCommandStore safe)
|
||||
{
|
||||
if (action == Action.FAILURE)
|
||||
throw new SimulatedFault("Operation failed for keys " + keys());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static class SimulatedLoadFunctionWrapper implements FunctionWrapper
|
||||
{
|
||||
final Supplier<Action> actions;
|
||||
|
|
|
|||
|
|
@ -505,8 +505,8 @@ public class CommandsForKeySerializerTest
|
|||
{
|
||||
int next = source.nextInt(commands.size());
|
||||
Command command = commands.get(next);
|
||||
if (command.txnId.isSyncPoint()) cfk = cfk.registerUnmanaged(new TestSafeCommandStore(ExecutionContext.contextFor(command.txnId(), "Test")), new TestSafeCommand(command), REGISTER).cfk();
|
||||
else cfk = cfk.update(new TestSafeCommandStore(ExecutionContext.contextFor(command.txnId(), "Test")), command).cfk();
|
||||
if (command.txnId.isSyncPoint()) cfk = cfk.registerUnmanaged(new TestSafeCommandStore(ExecutionContext.unsequenced(command.txnId(), "Test")), new TestSafeCommand(command), REGISTER).cfk();
|
||||
else cfk = cfk.update(new TestSafeCommandStore(ExecutionContext.unsequenced(command.txnId(), "Test")), command).cfk();
|
||||
commands.set(next, commands.get(commands.size() - 1));
|
||||
commands.remove(commands.size() - 1);
|
||||
}
|
||||
|
|
@ -547,33 +547,10 @@ public class CommandsForKeySerializerTest
|
|||
|
||||
static class TestSafeCommand extends SafeCommand
|
||||
{
|
||||
final Command command;
|
||||
TestSafeCommand(Command command)
|
||||
{
|
||||
super(command.txnId);
|
||||
this.command = command;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Command current()
|
||||
{
|
||||
return command;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markUnsafe()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUnsafe()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void set(Command command)
|
||||
{
|
||||
current = command;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -704,7 +681,7 @@ public class CommandsForKeySerializerTest
|
|||
{
|
||||
public TestSafeCommandStore(ExecutionContext context)
|
||||
{
|
||||
super(context, TestCommandStore.INSTANCE);
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override protected CommandStoreCaches tryGetCaches() { return null; }
|
||||
|
|
@ -712,6 +689,7 @@ public class CommandsForKeySerializerTest
|
|||
@Override protected SafeCommandsForKey add(SafeCommandsForKey safeCfk, CommandStoreCaches caches) { return null; }
|
||||
@Override protected SafeCommand getInternal(TxnId txnId) { return null; }
|
||||
@Override protected SafeCommandsForKey getInternal(RoutingKey key) { return null; }
|
||||
@Override public CommandStore commandStore() { return TestCommandStore.INSTANCE; }
|
||||
@Override public DataStore dataStore() { return null; }
|
||||
@Override public Agent agent() { return null; }
|
||||
@Override public ProgressLog progressLog() { return null; }
|
||||
|
|
|
|||
Loading…
Reference in New Issue