/* * Copyright (c) 2008-2017, Hazelcast, Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.hazelcast.map; import com.hazelcast.concurrent.lock.LockService; import com.hazelcast.concurrent.lock.LockServiceImpl; import com.hazelcast.concurrent.lock.LockStoreContainer; import com.hazelcast.config.Config; import com.hazelcast.core.HazelcastInstance; import com.hazelcast.core.IMap; import com.hazelcast.spi.impl.NodeEngineImpl; import com.hazelcast.test.AssertTask; import com.hazelcast.test.HazelcastParallelClassRunner; import com.hazelcast.test.HazelcastTestSupport; import com.hazelcast.test.TestHazelcastInstanceFactory; import com.hazelcast.test.annotation.NightlyTest; import com.hazelcast.test.annotation.ParallelTest; import com.hazelcast.test.annotation.QuickTest; import com.hazelcast.transaction.TransactionException; import org.junit.Test; import org.junit.experimental.categories.Category; import org.junit.runner.RunWith; import java.util.Random; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @RunWith(HazelcastParallelClassRunner.class) @Category({QuickTest.class, ParallelTest.class}) public class MapLockTest extends HazelcastTestSupport { @Test public void testIsLocked_afterDestroy() { final IMap<String, String> map = getMap(); final String key = randomString(); map.lock(key); map.destroy(); assertFalse(map.isLocked(key)); } @Test public void testIsLocked_afterDestroy_whenMapContainsKey() { final IMap<String, String> map = getMap(); final String key = randomString(); map.put(key, "value"); map.lock(key); map.destroy(); assertFalse(map.isLocked(key)); } @Test public void testBackupDies() throws TransactionException { Config config = getConfig(); TestHazelcastInstanceFactory factory = createHazelcastInstanceFactory(2); final HazelcastInstance h1 = factory.newHazelcastInstance(config); final HazelcastInstance h2 = factory.newHazelcastInstance(config); final IMap<Integer, Integer> map1 = h1.getMap("testBackupDies"); final int size = 50; final CountDownLatch latch = new CountDownLatch(1); Runnable runnable = new Runnable() { public void run() { for (int i = 0; i < size; i++) { map1.lock(i); sleepMillis(100); } for (int i = 0; i < size; i++) { assertTrue(map1.isLocked(i)); } for (int i = 0; i < size; i++) { map1.unlock(i); } for (int i = 0; i < size; i++) { assertFalse(map1.isLocked(i)); } latch.countDown(); } }; new Thread(runnable).start(); try { sleepSeconds(1); h2.shutdown(); assertTrue(latch.await(30, TimeUnit.SECONDS)); for (int i = 0; i < size; i++) { assertFalse(map1.isLocked(i)); } } catch (InterruptedException ignored) { } } @Test public void testLockEviction() throws Exception { final String name = randomString(); final TestHazelcastInstanceFactory nodeFactory = createHazelcastInstanceFactory(2); final Config config = getConfig(); final HazelcastInstance instance1 = nodeFactory.newHazelcastInstance(config); final HazelcastInstance instance2 = nodeFactory.newHazelcastInstance(config); warmUpPartitions(instance2, instance1); final IMap<Integer, Integer> map = instance1.getMap(name); map.put(1, 1); map.lock(1, 1, TimeUnit.SECONDS); assertTrue(map.isLocked(1)); final CountDownLatch latch = new CountDownLatch(1); Thread t = new Thread(new Runnable() { public void run() { map.lock(1); latch.countDown(); } }); t.start(); assertTrue(latch.await(10, TimeUnit.SECONDS)); } @Test(expected = IllegalArgumentException.class) public void testLockTTL_whenZeroTimeout() throws Exception { final IMap<String, String> mm = getMap(); final String key = "Key"; mm.lock(key, 0, TimeUnit.SECONDS); } @Test public void testLockEviction2() throws Exception { final TestHazelcastInstanceFactory nodeFactory = createHazelcastInstanceFactory(2); final Config config = getConfig(); final HazelcastInstance instance1 = nodeFactory.newHazelcastInstance(config); final HazelcastInstance instance2 = nodeFactory.newHazelcastInstance(config); warmUpPartitions(instance2, instance1); final String name = randomString(); final IMap<Integer, Integer> map = instance1.getMap(name); Random rand = new Random(); for (int i = 0; i < 5; i++) { map.lock(i, rand.nextInt(5) + 1, TimeUnit.SECONDS); } final CountDownLatch latch = new CountDownLatch(5); Thread t = new Thread(new Runnable() { public void run() { for (int i = 0; i < 5; i++) { map.lock(i); latch.countDown(); } } }); t.start(); assertTrue(latch.await(10, TimeUnit.SECONDS)); } @Test public void testLockMigration() throws Exception { final TestHazelcastInstanceFactory nodeFactory = createHazelcastInstanceFactory(3); final Config config = getConfig(); final HazelcastInstance instance1 = nodeFactory.newHazelcastInstance(config); final String name = randomString(); final IMap<Object, Object> map = instance1.getMap(name); for (int i = 0; i < 1000; i++) { map.lock(i); } final HazelcastInstance instance2 = nodeFactory.newHazelcastInstance(config); final HazelcastInstance instance3 = nodeFactory.newHazelcastInstance(config); Thread.sleep(3000); final CountDownLatch latch = new CountDownLatch(1000); Thread t = new Thread(new Runnable() { public void run() { for (int i = 0; i < 1000; i++) { if (map.isLocked(i)) { latch.countDown(); } } } }); t.start(); assertTrue(latch.await(10, TimeUnit.SECONDS)); } @Category(NightlyTest.class) @Test public void testLockEvictionWithMigration() throws Exception { final TestHazelcastInstanceFactory nodeFactory = createHazelcastInstanceFactory(3); final Config config = getConfig(); final HazelcastInstance instance1 = nodeFactory.newHazelcastInstance(config); final String name = randomString(); final IMap<Integer, Object> map = instance1.getMap(name); for (int i = 0; i < 1000; i++) { map.lock(i, 20, TimeUnit.SECONDS); } final HazelcastInstance instance2 = nodeFactory.newHazelcastInstance(config); final HazelcastInstance instance3 = nodeFactory.newHazelcastInstance(config); for (int i = 0; i < 1000; i++) { assertTrue(map.isLocked(i)); } final CountDownLatch latch = new CountDownLatch(1000); Thread t = new Thread(new Runnable() { public void run() { for (int i = 0; i < 1000; i++) { map.lock(i); latch.countDown(); } } }); t.start(); assertOpenEventually(latch); } @Test(expected = IllegalMonitorStateException.class) public void testLockOwnership() throws Exception { final TestHazelcastInstanceFactory nodeFactory = createHazelcastInstanceFactory(2); final Config config = getConfig(); String name = randomString(); final HazelcastInstance node1 = nodeFactory.newHazelcastInstance(config); final HazelcastInstance node2 = nodeFactory.newHazelcastInstance(config); final IMap<Integer, Object> map1 = node1.getMap(name); final IMap<Integer, Object> map2 = node2.getMap(name); map1.lock(1); map2.unlock(1); } @Test public void testAbsentKeyIsLocked() throws Exception { final TestHazelcastInstanceFactory nodeFactory = createHazelcastInstanceFactory(2); Config config = getConfig(); final String name = randomString(); final String key = "KEY"; final String val = "VAL_2"; final HazelcastInstance node1 = nodeFactory.newHazelcastInstance(config); final HazelcastInstance node2 = nodeFactory.newHazelcastInstance(config); final IMap<String, String> map1 = node1.getMap(name); final IMap<String, String> map2 = node2.getMap(name); map1.lock(key); boolean putResult = map2.tryPut(key, val, 2, TimeUnit.SECONDS); assertFalse("the result of try put should be false as the absent key is locked", putResult); assertTrueEventually(new AssertTask() { public void run() { assertEquals("the key should be absent ", null, map1.get(key)); assertEquals("the key should be absent ", null, map2.get(key)); } }); } @Test public void testLockTTLKey() { final IMap<String, String> map = getMap(); final String key = "key"; final String val = "val"; final int TTL_SEC = 1; map.put(key, val, TTL_SEC, TimeUnit.SECONDS); map.lock(key); sleepSeconds(TTL_SEC * 2); assertEquals("TTL of KEY has expired, KEY is locked, we expect VAL", val, map.get(key)); map.unlock(key); assertEquals("TTL of KEY has expired, KEY is unlocked, we expect null", null, map.get(key)); } @Test public void testClear_withLockedKey() { final IMap<String, String> map = getMap(); final String KEY = "key"; final String VAL = "val"; map.put(KEY, VAL); map.lock(KEY); map.clear(); assertEquals("a locked key should not be removed by map clear", false, map.isEmpty()); assertEquals("a key present in a map, should be locked after map clear", true, map.isLocked(KEY)); } /** * Do not use ungraceful node.getLifecycleService().terminate(), because that leads * backup inconsistencies between nodes and eventually this test will fail. * Instead use graceful node.getLifecycleService().shutdown(). */ @Test public void testClear_withLockedKey_whenNodeShutdown() { Config config = getConfig(); final TestHazelcastInstanceFactory nodeFactory = createHazelcastInstanceFactory(2); final HazelcastInstance node1 = nodeFactory.newHazelcastInstance(config); final HazelcastInstance node2 = nodeFactory.newHazelcastInstance(config); final String mapName = randomString(); final IMap<Object, Object> map = node2.getMap(mapName); for (int i = 0; i < 1000; i++) { map.put(i, i); } String key = generateKeyOwnedBy(node2); map.put(key, "value"); map.lock(key); final CountDownLatch cleared = new CountDownLatch(1); new Thread() { public void run() { map.clear(); cleared.countDown(); } }.start(); assertOpenEventually(cleared); node1.getLifecycleService().shutdown(); assertTrue("a key present in a map, should be locked after map clear", map.isLocked(key)); assertEquals("unlocked keys not removed", 1, map.size()); } @Test public void testTryLockLeaseTime_whenLockFree() throws InterruptedException { IMap<String, String> map = getMap(); String key = randomString(); boolean isLocked = map.tryLock(key, 1000, TimeUnit.MILLISECONDS, 1000, TimeUnit.MILLISECONDS); assertTrue(isLocked); } @Test public void testTryLockLeaseTime_whenLockAcquiredByOther() throws InterruptedException { final IMap<String, String> map = getMap(); final String key = randomString(); Thread thread = new Thread() { public void run() { map.lock(key); } }; thread.start(); thread.join(); boolean isLocked = map.tryLock(key, 1000, TimeUnit.MILLISECONDS, 1000, TimeUnit.MILLISECONDS); assertFalse(isLocked); } @Test public void testTryLockLeaseTime_lockIsReleasedEventually() throws InterruptedException { final IMap<String, String> map = getMap(); final String key = randomString(); map.tryLock(key, 1000, TimeUnit.MILLISECONDS, 1000, TimeUnit.MILLISECONDS); assertTrueEventually(new AssertTask() { @Override public void run() throws Exception { assertFalse(map.isLocked(key)); } }, 30); } /** * See issue #4888 */ @Test public void lockStoreShouldBeRemoved_whenMapIsDestroyed() { HazelcastInstance instance = createHazelcastInstance(getConfig()); IMap<Integer, Integer> map = instance.getMap(randomName()); for (int i = 0; i < 1000; i++) { map.lock(i); } map.destroy(); NodeEngineImpl nodeEngine = getNodeEngineImpl(instance); LockServiceImpl lockService = nodeEngine.getService(LockService.SERVICE_NAME); int partitionCount = nodeEngine.getPartitionService().getPartitionCount(); for (int i = 0; i < partitionCount; i++) { LockStoreContainer lockContainer = lockService.getLockContainer(i); assertEquals("LockStores should be empty", 0, lockContainer.getLockStores().size()); } } protected <K, V> IMap<K, V> getMap() { HazelcastInstance instance = createHazelcastInstance(getConfig()); return instance.getMap(randomString()); } }