package org.radargun.stages.cache.background; import java.util.HashMap; import java.util.Map; import org.radargun.Operation; import org.radargun.stages.helpers.Range; import org.radargun.stats.Request; import org.radargun.traits.BasicOperations; import org.radargun.traits.ConditionalOperations; /** * This logic operates on {@link SharedLogValue shared log values} * and requires {@link ConditionalOperations} on the cache. * With this setup, multiple stressors can change one log value concurrently. * * @author Radim Vansa <rvansa@redhat.com> */ class SharedLogLogic extends AbstractLogLogic<SharedLogValue> { private final ConditionalOperations.Cache nonTxConditionalCache; private ConditionalOperations.Cache conditionalCache; SharedLogLogic(BackgroundOpsManager manager, Range range) { super(manager, range); nonTxConditionalCache = manager.getConditionalCache(); if (transactionSize <= 0) { conditionalCache = nonTxConditionalCache; } } @Override protected boolean invokeLogic(long keyId) throws Exception { Operation operation = getOperation(operationTypeRandom); // In shared mode, we can't ever atomically modify the two keys (main and backup) to have only // one of them with the actual value (this is not true even for private mode but there the moment // when we move the value from main to backup or vice versa does not cause any problem, because the // worst thing is to read slightly outdated value). However, here the invariant holds that the operation // must be recorded in at least one of the entries, but the situation with both of these having // some value is valid (although, we try to evade it be conditionally removing one of them in each // logic step). SharedLogValue prevValue, backupValue, nextValue; do { prevValue = checkedGetValue(keyId); backupValue = checkedGetValue(~keyId); nextValue = getNextValue(prevValue, backupValue); if (stressor.isTerminated() || stressor.isInterrupted()) { return false; } } while (nextValue == null); // now for modify operations, execute it if (operation == BasicOperations.PUT) { if (checkedPutValue(keyId, prevValue, nextValue)) { if (backupValue != null) { delayedRemoveValue(~keyId, backupValue); } } else { return false; } } else if (operation == BasicOperations.REMOVE) { if (checkedPutValue(~keyId, backupValue, nextValue)) { if (prevValue != null) { delayedRemoveValue(keyId, prevValue); } } else { return false; } } else { // especially GETs are not allowed here, because these would break the deterministic order // - each operationId must be written somewhere throw new UnsupportedOperationException("Only PUT and REMOVE operations are allowed for this logic."); } return true; } private SharedLogValue getNextValue(SharedLogValue prevValue, SharedLogValue backupValue) throws StressorException, BreakTxRequest { if (prevValue == null && backupValue == null) { return new SharedLogValue(stressor.id, operationId); } else if (prevValue != null && backupValue != null) { SharedLogValue joinValue = prevValue.join(backupValue); if (joinValue.size() >= manager.getLogLogicConfiguration().getValueMaxSize()) { return filterAndAddOperation(joinValue); } else { return joinValue.with(stressor.id, operationId); } } SharedLogValue value = prevValue != null ? prevValue : backupValue; if (value.size() < manager.getLogLogicConfiguration().getValueMaxSize()) { return value.with(stressor.id, operationId); } else { return filterAndAddOperation(value); } } private SharedLogValue filterAndAddOperation(SharedLogValue value) throws StressorException, BreakTxRequest { Map<Integer, Long> operationIds = getCheckedOperations(value.minFrom(stressor.id)); SharedLogValue filtered = value.with(stressor.id, operationId, operationIds); if (filtered.size() > manager.getLogLogicConfiguration().getValueMaxSize()) { return null; } else { return filtered; } } protected Map<Integer, Long> getCheckedOperations(long operationId) throws StressorException, BreakTxRequest { Map<Integer, Long> minCheckedOperations = new HashMap<>(); for (int stressorId = 0; stressorId < manager.getGeneralConfiguration().getNumThreads() * manager.getSlaveState().getGroupSize(); ++stressorId) { minCheckedOperations.put(stressorId, getCheckedOperation(stressorId, operationId)); } return minCheckedOperations; } @Override protected void startTransaction() { ongoingTx = manager.newTransaction(); basicCache = ongoingTx.wrap(nonTxBasicCache); conditionalCache = ongoingTx.wrap(nonTxConditionalCache); ongoingTx.begin(); } @Override protected void clearTransaction() { super.clearTransaction(); conditionalCache = null; } private SharedLogValue checkedGetValue(long keyId) throws Exception { return (SharedLogValue) stressor.stats.startRequest().exec(BasicOperations.GET, () -> basicCache.get(keyGenerator.generateKey(keyId)), prevValue -> { if (prevValue != null && !(prevValue instanceof SharedLogValue)) { log.error("Value is not an instance of SharedLogValue: " + prevValue); throw new IllegalStateException(); } return true; }); } private boolean checkedPutValue(long keyId, SharedLogValue oldValue, SharedLogValue newValue) throws Exception { Request request = stressor.stats.startRequest(); if (oldValue == null) { return request.exec(ConditionalOperations.PUT_IF_ABSENT, () -> conditionalCache.putIfAbsent(keyGenerator.generateKey(keyId), newValue)); } else { return request.exec(ConditionalOperations.REPLACE, () -> conditionalCache.replace(keyGenerator.generateKey(keyId), oldValue, newValue)); } } @Override protected boolean checkedRemoveValue(long keyId, SharedLogValue oldValue) throws Exception { return stressor.stats.startRequest().exec(ConditionalOperations.REMOVE, () -> conditionalCache.remove(keyGenerator.generateKey(keyId), oldValue)); } }