/* * ModeShape (http://www.modeshape.org) * * 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 org.modeshape.jcr.locking; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.util.ArrayList; import java.util.List; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import java.util.stream.IntStream; import org.junit.Before; import org.junit.Test; /** * Unit test for {@link StandaloneLockingService} * * @author Horia Chiorean (hchiorea@redhat.com) */ public class StandaloneLockingServiceTest { private LockingService service; private ExecutorService executors; @Before public void before() { service = newLockingService(); executors = Executors.newFixedThreadPool(3); } public void after() throws Exception { executors.shutdownNow(); service.shutdown(); } @Test public void shouldLockMultipleLocks() throws Exception { String[] lockNames = new String[] { "lock1", "lock2" }; assertTrue(service.tryLock(lockNames)); assertTrue(service.unlock(lockNames)); } @Test public void shouldLockDifferentNamesFromDifferentThreads() throws Exception { CompletableFuture.runAsync(() -> { assertLock(service, true, "lock1"); assertTrue(service.unlock("lock1")); }).thenRunAsync(() -> { assertLock(service, true, "lock2"); assertTrue(service.unlock("lock2")); }, executors).get(); } @Test public void shuttingDownShouldReleaseLocks() throws Exception { assertTrue(service.tryLock("lock1")); assertTrue(service.tryLock("lock2")); assertTrue(service.shutdown()); assertFalse(service.shutdown()); } @Test public void shouldFailIfLocksAlreadyHeld() throws Exception { assertLock(service, true, "lock1", "lock2"); CompletableFuture.runAsync(() -> assertLock(service, false, "lock2", "lock1"), executors) .thenRunAsync(() -> assertLock(service, false, "lock1", "lock2"), executors) .get(); assertTrue(service.unlock("lock1", "lock2")); } @Test public void shouldReleaseAllLocksWhenFailingToAcquireOne() throws Exception { CompletableFuture.runAsync(() -> assertLock(service, true, "lock1", "lock2", "lock3")) .thenRunAsync(() -> assertLock(service, false, "lock4", "lock5", "lock2"), executors) .thenRun(() -> { assertLock(service, true, "lock4", "lock5"); assertTrue(service.unlock("lock4", "lock5")); }).get(); } @Test public void unlockShouldReleaseReentrantLocks() throws Exception { String[] locks = { "lock1", "lock2", "lock3" }; CompletableFuture.runAsync(() -> { assertLock(service, true, locks); assertLock(service, true, locks); assertLock(service, true, locks); assertTrue(service.unlock(locks)); }).get(); assertTrue(service.tryLock(locks)); assertTrue(service.unlock(locks)); } @Test public void locksShouldBeExclusiveAcrossMultipleThreads() throws Exception { String commonLock = "3293af3317f1e7/"; int threadCount = 100; int uniqueLocksPerThread = 3; List<String> results = new ArrayList<>(); ExecutorService executorService = Executors.newFixedThreadPool(threadCount); IntStream.range(0, threadCount) .forEach(i -> executorService.submit(() -> { lockAndUnlock(service, commonLock, uniqueLocksPerThread, results); return null; })); executorService.shutdown(); if (!executorService.awaitTermination(10, TimeUnit.SECONDS)) { fail("Could obtain locks in an orderly amount of time.."); executorService.shutdownNow(); } else { assertEquals("exclusive access was not ensured", threadCount * uniqueLocksPerThread, results.size()); } } private void lockAndUnlock(LockingService service, String commonLock, int uniqueLocksCount, List<String> accumulator) { List<String> uniqueLocks = IntStream.range(0, uniqueLocksCount).mapToObj(i -> UUID.randomUUID().toString()).collect(Collectors.toList()); uniqueLocks.add(commonLock); String[] locks = uniqueLocks.toArray(new String[uniqueLocks.size()]); try { if (service.tryLock(15, TimeUnit.SECONDS, locks)) { uniqueLocks.remove(commonLock); accumulator.addAll(uniqueLocks); service.unlock(locks); } else { fail("locks should've been obtained by now..."); } } catch (InterruptedException e) { fail("interrupted..."); } } protected LockingService newLockingService() { return new StandaloneLockingService(); } protected void assertLock(LockingService service, boolean successful, String...names) { try { boolean result = service.tryLock(names); assertEquals("lock operation failed", successful, result); } catch (InterruptedException e) { Thread.interrupted(); fail("interrupted..."); } } }