package org.radargun.stages.cache.background;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.radargun.logging.Log;
import org.radargun.logging.LogFactory;
import org.radargun.stages.cache.generators.KeyGenerator;
import org.radargun.stages.cache.generators.StringKeyGenerator;
import org.radargun.stages.helpers.Range;
import org.radargun.state.ServiceListener;
import org.radargun.state.SlaveState;
import org.radargun.traits.BasicOperations;
import org.radargun.traits.CacheInformation;
import org.radargun.traits.CacheListeners;
import org.radargun.traits.ConditionalOperations;
import org.radargun.traits.Debugable;
import org.radargun.traits.Lifecycle;
import org.radargun.traits.Transactional;
import org.radargun.utils.TimeService;
/**
* Manages background stressors and log checkers (start/stop/check for errors).
*
* //TODO: more polishing to make this class agnostic to implemented logic (just pass configuration)
*
* @author Michal Linhard <mlinhard@redhat.com>
* @author Radim Vansa <rvansa@redhat.com>
*/
public class BackgroundOpsManager implements ServiceListener {
/**
* Key to SlaveState to retrieve BackgroundOpsManager instance and to MasterState to retrieve results.
*/
private static final String PREFIX = "BackgroundOps.";
public static final String DEFAULT = "Default";
public static final String ALL = PREFIX + "All";
private static Log log = LogFactory.getLog(BackgroundOpsManager.class);
private GeneralConfiguration generalConfiguration;
private BackgroundStressorLogicConfiguration backgroundStressorLogicConfiguration;
private LogLogicConfiguration logLogicConfiguration;
private String name;
private SlaveState slaveState;
private boolean loaded = false;
private StressorRecordPool stressorRecordPool;
private FailureManager failureManager;
private ThreadManager threadManager;
private Lifecycle lifecycle;
private CacheListeners listeners;
private volatile BasicOperations.Cache basicCache;
private volatile Debugable.Cache debugableCache;
private volatile Transactional transactional;
private volatile ConditionalOperations.Cache conditionalCache;
private volatile CacheInformation.Cache cacheInfo;
private BackgroundOpsManager() {
}
public static BackgroundOpsManager getInstance(SlaveState slaveState, String name) {
return (BackgroundOpsManager) slaveState.get(PREFIX + name);
}
public static BackgroundOpsManager getOrCreateInstance(SlaveState slaveState, String name) {
BackgroundOpsManager instance = getInstance(slaveState, name);
if (instance == null) {
instance = new BackgroundOpsManager();
instance.name = name;
instance.slaveState = slaveState;
instance.failureManager = new FailureManager(instance);
instance.threadManager = new ThreadManager(instance);
slaveState.put(PREFIX + name, instance);
slaveState.addListener(instance);
List<BackgroundOpsManager> list = (List<BackgroundOpsManager>) slaveState.get(ALL);
if (list == null) {
slaveState.put(ALL, list = new ArrayList<>());
}
list.add(instance);
}
return instance;
}
public static BackgroundOpsManager getOrCreateInstance(SlaveState slaveState, String name,
GeneralConfiguration generalConfiguration,
BackgroundStressorLogicConfiguration backgroundStressorLogicConfiguration,
LogLogicConfiguration logLogicConfiguration) {
BackgroundOpsManager instance = getOrCreateInstance(slaveState, name);
instance.generalConfiguration = generalConfiguration;
instance.backgroundStressorLogicConfiguration = backgroundStressorLogicConfiguration;
instance.logLogicConfiguration = logLogicConfiguration;
instance.threadManager.initConfiguration();
instance.lifecycle = slaveState.getTrait(Lifecycle.class);
instance.listeners = slaveState.getTrait(CacheListeners.class);
instance.loadCaches();
return instance;
}
public static List<BackgroundOpsManager> getAllInstances(SlaveState slaveState) {
List<BackgroundOpsManager> instances = (List<BackgroundOpsManager>) slaveState.get(ALL);
return instances != null ? instances : Collections.EMPTY_LIST;
}
private void loadCaches() {
if (lifecycle != null && !lifecycle.isRunning()) {
log.warn("Can't load caches, service is not running");
return;
}
basicCache = slaveState.getTrait(BasicOperations.class).getCache(generalConfiguration.cacheName);
ConditionalOperations conditionalOperations = slaveState.getTrait(ConditionalOperations.class);
conditionalCache = conditionalOperations == null ? null : conditionalOperations.getCache(generalConfiguration.cacheName);
Debugable debugable = slaveState.getTrait(Debugable.class);
debugableCache = debugable == null ? null : debugable.getCache(generalConfiguration.cacheName);
if (generalConfiguration.transactionSize > 0) {
transactional = slaveState.getTrait(Transactional.class);
if (transactional == null) {
throw new IllegalArgumentException("Transactions are set on but the service does not provide transactions");
} else if (transactional.getConfiguration(generalConfiguration.cacheName) == Transactional.Configuration.NON_TRANSACTIONAL) {
throw new IllegalArgumentException("Transactions are set on but the cache is not configured as transactional");
}
}
CacheInformation cacheInformation = slaveState.getTrait(CacheInformation.class);
cacheInfo = cacheInformation == null ? null : cacheInformation.getCache(generalConfiguration.cacheName);
}
private void unloadCaches() {
basicCache = null;
conditionalCache = null;
debugableCache = null;
cacheInfo = null;
}
public Logic createLogic(int index) {
int numThreads = generalConfiguration.numThreads;
if (generalConfiguration.sharedKeys) {
if (logLogicConfiguration.enabled) {
return new SharedLogLogic(this, new Range(generalConfiguration.keyIdOffset, generalConfiguration.keyIdOffset + generalConfiguration.numEntries));
} else {
throw new IllegalArgumentException("The logic cannot use shared keys");
}
} else {
// TODO: we may have broken totalThreads for logic with dead slaves preloading
int totalThreads = numThreads * slaveState.getGroupSize();
Range range = Range.divideRange(generalConfiguration.numEntries, totalThreads, numThreads * slaveState.getIndexInGroup() + index).shift(generalConfiguration.keyIdOffset);
if (logLogicConfiguration.enabled) {
int threadId = numThreads * slaveState.getIndexInGroup() + index;
log.tracef("Stressor %d has range %s", threadId, range);
return new PrivateLogLogic(this, range);
} else {
// TODO: remove preloading from background stressors at all, use load-data instead
List<Integer> deadSlaves = backgroundStressorLogicConfiguration.loadDataForDeadSlaves;
List<List<Range>> rangesForThreads = null;
int liveId = slaveState.getSlaveIndex();
if (!loaded && deadSlaves != null && !deadSlaves.isEmpty()) {
List<Range> deadSlavesKeyRanges = new ArrayList<Range>(deadSlaves.size() * numThreads);
for (int deadSlave : deadSlaves) {
// key ranges for the current dead slave
for (int deadThread = 0; deadThread < numThreads; ++deadThread) {
deadSlavesKeyRanges.add(Range.divideRange(generalConfiguration.numEntries, totalThreads, deadSlave * numThreads + deadThread));
}
if (deadSlave < slaveState.getSlaveIndex()) liveId--;
}
rangesForThreads = Range.balance(deadSlavesKeyRanges, (slaveState.getClusterSize() - deadSlaves.size()) * numThreads);
}
List<Range> deadRanges = rangesForThreads == null ? null : rangesForThreads.get(index + numThreads * liveId);
return new BackgroundStressorLogic(this, range, deadRanges, loaded);
}
}
}
public synchronized void createStressorRecordPool() {
if (stressorRecordPool != null) {
log.debug("Checker pool already exists, not creating another.");
// When service has been stopped for a longer period of time than LogLogicConfiguration.noProgressTimeout, this can lead
// to no progress timeouts causing test failures. Avoid this by re-setting records' lastSuccessfulCheck timestamps.
// When checkers truly show no progress, it will be reliably detected by ThreadManager.waitUntilChecked combined with
// ThreadManager.waitForProgress.
if (logLogicConfiguration.ignoreDeadCheckers) {
for (StressorRecord stressorRecord : stressorRecordPool.getAvailableRecords()) {
stressorRecord.setLastSuccessfulCheckTimestamp(TimeService.currentTimeMillis());
}
}
return;
}
int totalThreads = slaveState.getGroupSize() * generalConfiguration.numThreads;
List<StressorRecord> stressorRecords = new ArrayList<>();
// Initialize stressor records
if (generalConfiguration.sharedKeys) {
for (int threadId = 0; threadId < totalThreads; ++threadId) {
Range range = new Range(generalConfiguration.keyIdOffset, generalConfiguration.keyIdOffset + generalConfiguration.numEntries);
stressorRecords.add(new StressorRecord(threadId, range));
log.tracef("Record for threadId %d has range %s", threadId, range);
}
stressorRecordPool = new StressorRecordPool(totalThreads, stressorRecords, this);
} else {
for (int threadId = 0; threadId < totalThreads; ++threadId) {
Range range = Range.divideRange(generalConfiguration.getNumEntries(), totalThreads, threadId).shift(generalConfiguration.keyIdOffset);
stressorRecords.add(new StressorRecord(threadId, range));
log.tracef("Record for threadId %d has range %s", threadId, range);
}
stressorRecordPool = new StressorRecordPool(totalThreads, stressorRecords, this);
}
}
public synchronized void createFailureManager() {
if (failureManager != null) {
log.debug("Failure holder already exists, not creating another.");
return;
}
failureManager = new FailureManager(this);
}
public KeyGenerator getKeyGenerator() {
KeyGenerator keyGenerator = (KeyGenerator) slaveState.get(KeyGenerator.KEY_GENERATOR);
if (keyGenerator == null) {
keyGenerator = new StringKeyGenerator();
slaveState.put(KeyGenerator.KEY_GENERATOR, keyGenerator);
}
return keyGenerator;
}
// Starts stressor and checker threads. If ignoreDeadCheckers is specified, ThreadManager.KeepAliveTask is scheduled.
public synchronized void startBackgroundThreads() {
if (logLogicConfiguration.enabled) {
createStressorRecordPool();
createFailureManager();
}
threadManager.startBackgroundThreads();
}
// Stops stressor and checker threads, including ThreadManager.KeepAliveTask.
public synchronized void stopBackgroundThreads() {
threadManager.stopBackgroundThreads();
}
// Waits until all stressor threads load data. Applies to BackgroundStressorLogic only.
public synchronized void waitUntilLoaded() throws InterruptedException {
threadManager.waitUntilLoaded();
}
// 1. Stop stressor threads.
// 2. Wait until checkers check all operations up to last operation written by stressor.
// 3. Stop checker threads.
public String waitUntilChecked() {
return threadManager.waitUntilChecked();
}
// Wait until a change in last performed operation of a stressor is detected.
public boolean waitForProgress() {
return threadManager.waitForProgress();
}
// Start stressor and checker threads.
public void resumeAfterChecked() {
threadManager.resumeAfterChecked();
}
// Check whether an error was detected in test run and return it. Otherwise return null.
public synchronized String getError(boolean failuresOnly) {
return failureManager.getError(failuresOnly);
}
public boolean isSlaveAlive(int slaveId) {
Long keepAliveTimestamp = null;
try {
keepAliveTimestamp = (Long) basicCache.get("__keepAlive_" + slaveId);
} catch (Exception e) {
log.error("Failed to retrieve the keep alive timestamp", e);
return true;
}
return keepAliveTimestamp != null && keepAliveTimestamp > TimeService.currentTimeMillis() - generalConfiguration.deadSlaveTimeout;
}
public Transactional.Transaction newTransaction() {
return transactional.getTransaction();
}
public BasicOperations.Cache getBasicCache() {
return basicCache;
}
public Debugable.Cache getDebugableCache() {
return debugableCache;
}
public ConditionalOperations.Cache getConditionalCache() {
return conditionalCache;
}
public CacheListeners getListeners() {
return listeners;
}
public String getName() {
return name;
}
public void setLoaded(boolean loaded) {
this.loaded = loaded;
}
public SlaveState getSlaveState() {
return slaveState;
}
public GeneralConfiguration getGeneralConfiguration() {
return generalConfiguration;
}
public BackgroundStressorLogicConfiguration getBackgroundStressorLogicConfiguration() {
return backgroundStressorLogicConfiguration;
}
public LogLogicConfiguration getLogLogicConfiguration() {
return logLogicConfiguration;
}
public CacheInformation.Cache getCacheInfo() {
return cacheInfo;
}
public Lifecycle getLifecycle() {
return lifecycle;
}
public StressorRecordPool getStressorRecordPool() {
return stressorRecordPool;
}
public FailureManager getFailureManager() {
return failureManager;
}
public ThreadManager getThreadManager() {
return threadManager;
}
// The following methods handle situation when service is started/stopped. In that case we may need to start/stop
// background threads, as they access the cache directly.
// TODO:
// a) clear listener on the BasicOperations trait
// b) deal with the fact that the clear can be executed
public static void beforeCacheClear(SlaveState slaveState) {
List<BackgroundOpsManager> instances = getAllInstances(slaveState);
for (BackgroundOpsManager instance : instances) {
instance.setLoaded(false);
}
}
@Override
public void afterServiceStart() {
setLoaded(true); // don't load data at this stage
loadCaches(); // the object returned by a trait may be invalid after restart
startBackgroundThreads();
}
@Override
public void beforeServiceStop(boolean graceful) {
stopBackgroundThreads();
unloadCaches();
}
@Override
public void afterServiceStop(boolean graceful) {}
}