/* * Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com * The software in this package is published under the terms of the CPAL v1.0 * license, a copy of which has been included with this distribution in the * LICENSE.txt file. */ package org.mule.runtime.core.internal.lock; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertThat; import org.mule.runtime.core.api.lock.LockProvider; import org.mule.runtime.api.store.ObjectAlreadyExistsException; import org.mule.runtime.api.store.ObjectStore; import org.mule.runtime.api.store.ObjectStoreException; import org.mule.runtime.core.config.i18n.CoreMessages; import org.mule.runtime.core.util.concurrent.Latch; import org.mule.tck.junit4.AbstractMuleTestCase; import java.io.Serializable; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import org.junit.Test; import org.mockito.Answers; import org.mockito.Mockito; import org.mockito.internal.verification.VerificationModeFactory; public class InstanceLockGroupTestCase extends AbstractMuleTestCase { public static final int THREAD_COUNT = 100; public static final int ITERATIONS_PER_THREAD = 100; private Latch threadStartLatch = new Latch(); private String sharedKeyA = "A"; private String sharedKeyB = "B"; private InstanceLockGroup instanceLockGroup = new InstanceLockGroup(new SingleServerLockProvider()); private InMemoryObjectStore objectStore = new InMemoryObjectStore(); private LockProvider mockLockProvider; @Test public void testLockUnlock() throws Exception { testHighConcurrency(false); } @Test public void testTryLockUnlock() throws Exception { testHighConcurrency(true); } @Test public void testWhenUnlockThenDestroy() throws Exception { lockUnlockThenDestroy(1); } @Test public void testWhenSeveralLockOneUnlockThenDestroy() throws Exception { lockUnlockThenDestroy(5); } private void lockUnlockThenDestroy(int lockTimes) { mockLockProvider = Mockito.mock(LockProvider.class, Answers.RETURNS_DEEP_STUBS.get()); InstanceLockGroup instanceLockGroup = new InstanceLockGroup(mockLockProvider); for (int i = 0; i < lockTimes; i++) { instanceLockGroup.lock("lockId"); } instanceLockGroup.unlock("lockId"); Mockito.verify(mockLockProvider, VerificationModeFactory.times(1)).createLock("lockId"); } private void testHighConcurrency(boolean useTryLock) throws InterruptedException, ObjectStoreException { List<Thread> threads = new ArrayList<Thread>(THREAD_COUNT * 2); for (int i = 0; i < THREAD_COUNT; i++) { IncrementKeyValueThread incrementKeyValueThread = new IncrementKeyValueThread(sharedKeyA, useTryLock); threads.add(incrementKeyValueThread); incrementKeyValueThread.start(); incrementKeyValueThread = new IncrementKeyValueThread(sharedKeyB, useTryLock); threads.add(incrementKeyValueThread); incrementKeyValueThread.start(); } threadStartLatch.release(); for (Thread thread : threads) { thread.join(); } assertThat(objectStore.retrieve(sharedKeyA), is(THREAD_COUNT * ITERATIONS_PER_THREAD)); assertThat(objectStore.retrieve(sharedKeyB), is(THREAD_COUNT * ITERATIONS_PER_THREAD)); } public class IncrementKeyValueThread extends Thread { private String key; private boolean useTryLock; private IncrementKeyValueThread(String key, boolean useTryLock) { super("Thread-" + key); this.key = key; this.useTryLock = useTryLock; } @Override public void run() { try { threadStartLatch.await(5000, TimeUnit.MILLISECONDS); for (int i = 0; i < ITERATIONS_PER_THREAD; i++) { if (Thread.interrupted()) { break; } if (useTryLock) { while (!instanceLockGroup.tryLock(key, 100, TimeUnit.MILLISECONDS)); } else { instanceLockGroup.lock(key); } try { Integer value; if (objectStore.contains(key)) { value = objectStore.retrieve(key); objectStore.remove(key); } else { value = 0; } objectStore.store(key, value + 1); } finally { instanceLockGroup.unlock(key); } } } catch (Exception e) { throw new RuntimeException(e); } } } public static class InMemoryObjectStore implements ObjectStore<Integer> { private Map<Serializable, Integer> store = new ConcurrentHashMap<Serializable, Integer>(); @Override public boolean contains(Serializable key) throws ObjectStoreException { return store.containsKey(key); } @Override public void store(Serializable key, Integer value) throws ObjectStoreException { if (store.containsKey(key)) { throw new ObjectAlreadyExistsException(CoreMessages.createStaticMessage("")); } store.put(key, value); } @Override public Integer retrieve(Serializable key) throws ObjectStoreException { return store.get(key); } @Override public Integer remove(Serializable key) throws ObjectStoreException { return store.remove(key); } @Override public void clear() throws ObjectStoreException { this.store.clear(); } @Override public boolean isPersistent() { return false; } } }