package org.infinispan.lock; import static java.lang.String.valueOf; import static org.testng.AssertJUnit.assertTrue; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.ConcurrentModificationException; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import javax.transaction.TransactionManager; import org.infinispan.Cache; import org.infinispan.configuration.cache.ConfigurationBuilder; import org.infinispan.manager.EmbeddedCacheManager; import org.infinispan.test.SingleCacheManagerTest; import org.infinispan.test.fwk.CleanupAfterMethod; import org.infinispan.test.fwk.TestCacheManagerFactory; import org.infinispan.transaction.LockingMode; import org.infinispan.util.concurrent.locks.LockManager; import org.infinispan.util.logging.Log; import org.infinispan.util.logging.LogFactory; import org.testng.annotations.Test; /** * Contributed by Dmitry Udalov * * @author Dmitry Udalov * @author Pedro Ruivo * @since 6.0 */ @Test(groups = "functional", testName = "lock.ExplicitUnlockTest") @CleanupAfterMethod public class ExplicitUnlockTest extends SingleCacheManagerTest { private static final Log log = LogFactory.getLog(ExplicitUnlockTest.class); private static final int NUMBER_OF_KEYS = 10; public void testLock() throws Exception { doTestLock(true, 10, 10); } public void testLockTwoTasks() throws Exception { doTestLock(true, 2, 10); } public void testLockNoExplicitUnlock() throws Exception { doTestLock(false, 10, 10); } public void testLockNoExplicitUnlockTwoTasks() throws Exception { doTestLock(false, 10, 10); } @Override protected EmbeddedCacheManager createCacheManager() throws Exception { ConfigurationBuilder builder = getDefaultStandaloneCacheConfig(true); builder.transaction().lockingMode(LockingMode.PESSIMISTIC); return TestCacheManagerFactory.createCacheManager(builder); } private void doTestLock(boolean withUnlock, int nThreads, long stepDelayMsec) throws Exception { for (int key = 1; key <= NUMBER_OF_KEYS; key++) { cache.put("" + key, "value"); } List<Future<Boolean>> results = new ArrayList<>(nThreads); for (int i = 1; i <= nThreads; i++) { results.add(fork(new Worker(i, cache, withUnlock, stepDelayMsec))); } boolean success = true; for (Future<Boolean> next : results) { success = success && next.get(30, TimeUnit.SECONDS); } assertTrue("All worker should complete without exceptions", success); assertNoTransactions(); for (int i = 0; i < NUMBER_OF_KEYS; ++i) { assertEventuallyNotLocked(cache, valueOf(i)); } } private static class Worker implements Callable<Boolean> { private static String lockKey = "0"; // there is no cached Object with such key private final Cache<Object, Object> cache; private final boolean withUnlock; private final long stepDelayMsec; private final int index; public Worker(int index, final Cache<Object, Object> cache, boolean withUnlock, long stepDelayMsec) { this.index = index; this.cache = cache; this.withUnlock = withUnlock; this.stepDelayMsec = stepDelayMsec; } @Override public Boolean call() throws Exception { boolean success; try { doRun(); success = true; } catch (Throwable t) { log.errorf(t, "Error in Worker[%s, unlock? %s]", index, withUnlock); success = false; } return success; } private void log(String method, String msg) { log.debugf("Worker[%s, unlock? %s] %s %s", index, withUnlock, method, msg); } private void doRun() throws Exception { final String methodName = "run"; TransactionManager mgr = cache.getAdvancedCache().getTransactionManager(); if (null == mgr) { throw new UnsupportedOperationException("TransactionManager was not configured for the cache " + cache.getName()); } mgr.begin(); try { if (acquireLock()) { log(methodName, "acquired lock"); // renaming all Objects from 1 to NUMBER_OF_KEYS String newName = "value-" + index; log(methodName, "Changing value to " + newName); for (int key = 1; key <= NUMBER_OF_KEYS; key++) { cache.put(valueOf(key), newName); Thread.sleep(stepDelayMsec); } validateCache(); if (withUnlock) { unlock(lockKey); } } else { log(methodName, "Failed to acquired lock"); } mgr.commit(); } catch (Exception t) { mgr.rollback(); throw t; } } private boolean acquireLock() { return cache.getAdvancedCache().lock(lockKey); } private boolean unlock(String resourceId) { LockManager lockManager = cache.getAdvancedCache().getLockManager(); Object lockOwner = lockManager.getOwner(resourceId); Collection<Object> keys = Collections.<Object>singletonList(resourceId); lockManager.unlock(keys, lockOwner); return true; } /** * Checks if all cache entries are consistent * * @throws InterruptedException */ private void validateCache() throws InterruptedException { String value = getCachedValue(1); for (int key = 1; key <= NUMBER_OF_KEYS; key++) { String nextValue = getCachedValue(key); if (!value.equals(nextValue)) { String msg = String.format("Cache inconsistent: value=%s, nextValue=%s", value, nextValue); log("validate_cache", msg); throw new ConcurrentModificationException(msg); } Thread.sleep(stepDelayMsec); } log("validate_cache", "passed: " + value); } private String getCachedValue(int index) { String value = (String) cache.get(valueOf(index)); if (null == value) { throw new ConcurrentModificationException("Missed entry for " + index); } return value; } } }