package org.keycloak.models.sessions.infinispan.initializer;
import org.infinispan.Cache;
import org.infinispan.configuration.cache.CacheMode;
import org.infinispan.configuration.cache.Configuration;
import org.infinispan.configuration.cache.ConfigurationBuilder;
import org.infinispan.configuration.cache.VersioningScheme;
import org.infinispan.configuration.global.GlobalConfigurationBuilder;
import org.infinispan.manager.DefaultCacheManager;
import org.infinispan.manager.EmbeddedCacheManager;
import org.infinispan.transaction.TransactionMode;
import org.infinispan.transaction.lookup.DummyTransactionManagerLookup;
import org.infinispan.util.concurrent.IsolationLevel;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
import javax.transaction.NotSupportedException;
import javax.transaction.Status;
import javax.transaction.SystemException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Unit tests to make sure our model caching concurrency model will work.
*
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
@Ignore
public class ConcurrencyVersioningTest {
public static abstract class AbstractThread implements Runnable {
EmbeddedCacheManager cacheManager;
boolean success;
CountDownLatch latch = new CountDownLatch(1);
public AbstractThread(EmbeddedCacheManager cacheManager) {
this.cacheManager = cacheManager;
}
public boolean isSuccess() {
return success;
}
public CountDownLatch getLatch() {
return latch;
}
}
public static class RemoveThread extends AbstractThread {
public RemoveThread(EmbeddedCacheManager cacheManager) {
super(cacheManager);
}
public void run() {
Cache<String, String> cache = cacheManager.getCache(InfinispanConnectionProvider.REALM_CACHE_NAME);
try {
startBatch(cache);
cache.remove("key");
//cache.getAdvancedCache().getTransactionManager().commit();
endBatch(cache);
success = true;
} catch (Exception e) {
success = false;
}
latch.countDown();
}
}
public static class UpdateThread extends AbstractThread {
public UpdateThread(EmbeddedCacheManager cacheManager) {
super(cacheManager);
}
public void run() {
Cache<String, String> cache = cacheManager.getCache(InfinispanConnectionProvider.REALM_CACHE_NAME);
try {
startBatch(cache);
cache.putForExternalRead("key", "value2");
//cache.getAdvancedCache().getTransactionManager().commit();
endBatch(cache);
success = true;
} catch (Exception e) {
success = false;
}
latch.countDown();
}
}
/**
* Tests that if remove executes before put, then put still succeeds.
*
* @throws Exception
*/
@Test
public void testGetRemovePutOnNonExisting() throws Exception {
final DefaultCacheManager cacheManager = getVersionedCacheManager();
ExecutorService executor = Executors.newSingleThreadExecutor();
RemoveThread removeThread = new RemoveThread(cacheManager);
Cache<String, String> cache = cacheManager.getCache(InfinispanConnectionProvider.REALM_CACHE_NAME);
cache.remove("key");
startBatch(cache);
cache.get("key");
executor.execute(removeThread);
removeThread.getLatch().await();
cache.putForExternalRead("key", "value1");
endBatch(cache);
Assert.assertEquals(cache.get("key"), "value1");
Assert.assertTrue(removeThread.isSuccess());
}
/**
* Test that if a put of an existing key is removed after the put and before tx commit, it is evicted
*
* @throws Exception
*/
@Test
public void testGetRemovePutOnExisting() throws Exception {
final DefaultCacheManager cacheManager = getVersionedCacheManager();
ExecutorService executor = Executors.newSingleThreadExecutor();
RemoveThread removeThread = new RemoveThread(cacheManager);
Cache<String, String> cache = cacheManager.getCache(InfinispanConnectionProvider.REALM_CACHE_NAME);
cache.put("key", "value0");
startBatch(cache);
cache.get("key");
executor.execute(removeThread);
removeThread.getLatch().await();
cache.put("key", "value1");
try {
endBatch(cache);
Assert.fail("Write skew should be detected");
} catch (Exception e) {
}
Assert.assertNull(cache.get("key"));
Assert.assertTrue(removeThread.isSuccess());
}
/**
* Test that if a put of an existing key is removed after the put and before tx commit, it is evicted
*
* @throws Exception
*/
@Test
public void testGetRemovePutEternalOnExisting() throws Exception {
final DefaultCacheManager cacheManager = getVersionedCacheManager();
ExecutorService executor = Executors.newSingleThreadExecutor();
RemoveThread removeThread = new RemoveThread(cacheManager);
Cache<String, String> cache = cacheManager.getCache(InfinispanConnectionProvider.REALM_CACHE_NAME);
cache.put("key", "value0");
startBatch(cache);
cache.get("key");
executor.execute(removeThread);
cache.putForExternalRead("key", "value1");
removeThread.getLatch().await();
try {
endBatch(cache);
// Assert.fail("Write skew should be detected");
} catch (Exception e) {
}
Assert.assertNull(cache.get("key"));
Assert.assertTrue(removeThread.isSuccess());
}
@Test
public void testPutExternalRemoveOnExisting() throws Exception {
final DefaultCacheManager cacheManager = getVersionedCacheManager();
ExecutorService executor = Executors.newSingleThreadExecutor();
RemoveThread removeThread = new RemoveThread(cacheManager);
Cache<String, String> cache = cacheManager.getCache(InfinispanConnectionProvider.REALM_CACHE_NAME);
cache.put("key", "value0");
startBatch(cache);
cache.putForExternalRead("key", "value1");
executor.execute(removeThread);
removeThread.getLatch().await();
try {
endBatch(cache);
// Assert.fail("Write skew should be detected");
} catch (Exception e) {
}
Assert.assertNull(cache.get("key"));
Assert.assertTrue(removeThread.isSuccess());
}
public static void startBatch(Cache<String, String> cache) {
try {
if (cache.getAdvancedCache().getTransactionManager().getStatus() == Status.STATUS_NO_TRANSACTION) {
System.out.println("begin");
cache.getAdvancedCache().getTransactionManager().begin();
}
} catch (NotSupportedException e) {
throw new RuntimeException(e);
} catch (SystemException e) {
throw new RuntimeException(e);
}
}
public static void endBatch(Cache<String, String> cache) {
boolean commit = true;
try {
if (cache.getAdvancedCache().getTransactionManager().getStatus() == Status.STATUS_ACTIVE) {
if (commit) {
cache.getAdvancedCache().getTransactionManager().commit();
} else {
cache.getAdvancedCache().getTransactionManager().rollback();
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
protected DefaultCacheManager getVersionedCacheManager() {
GlobalConfigurationBuilder gcb = new GlobalConfigurationBuilder();
boolean clustered = false;
boolean async = false;
boolean allowDuplicateJMXDomains = true;
if (clustered) {
gcb.transport().defaultTransport();
}
gcb.globalJmxStatistics().allowDuplicateDomains(allowDuplicateJMXDomains);
final DefaultCacheManager cacheManager = new DefaultCacheManager(gcb.build());
ConfigurationBuilder invalidationConfigBuilder = new ConfigurationBuilder();
invalidationConfigBuilder
//.invocationBatching().enable()
.transaction().transactionMode(TransactionMode.TRANSACTIONAL)
.transaction().transactionManagerLookup(new DummyTransactionManagerLookup())
.locking().isolationLevel(IsolationLevel.REPEATABLE_READ).writeSkewCheck(true).versioning().enable().scheme(VersioningScheme.SIMPLE);
//invalidationConfigBuilder.locking().isolationLevel(IsolationLevel.REPEATABLE_READ).writeSkewCheck(true).versioning().enable().scheme(VersioningScheme.SIMPLE);
if (clustered) {
invalidationConfigBuilder.clustering().cacheMode(async ? CacheMode.INVALIDATION_ASYNC : CacheMode.INVALIDATION_SYNC);
}
Configuration invalidationCacheConfiguration = invalidationConfigBuilder.build();
cacheManager.defineConfiguration(InfinispanConnectionProvider.REALM_CACHE_NAME, invalidationCacheConfiguration);
return cacheManager;
}
}