/*
* 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.assertFalse;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.modeshape.jcr.ClusteringHelper;
import org.modeshape.jcr.clustering.ClusteringService;
/**
* Test which verifies the locking logic of the {@link org.modeshape.jcr.locking.LockingService} as per the contract
* defined by {@link LockingService}
*
* @author Horia Chiorean (hchiorea@redhat.com)
*/
public class JGroupsLockingServiceTest extends StandaloneLockingServiceTest {
private static final int SERVICES_COUNT = 4;
private static List<ClusteringService> clusteringServices;
private List<LockingService> lockingServices = new ArrayList<>();
@BeforeClass
public static void beforeClass() throws Exception {
ClusteringHelper.bindJGroupsToLocalAddress();
clusteringServices = IntStream.range(0, SERVICES_COUNT)
.mapToObj(i -> ClusteringService.startStandalone("locking-cluster",
"config/cluster/jgroups-test-config.xml"))
.collect(Collectors.toList());
}
@AfterClass
public static void afterClass() throws Exception {
ClusteringHelper.removeJGroupsBindings();
clusteringServices.forEach(ClusteringService::shutdown);
clusteringServices.clear();
}
@After
public void after() throws Exception {
super.after();
lockingServices.forEach(LockingService::shutdown);
lockingServices.clear();
}
@Test
public void shouldAcquireDisjunctLocksInCluster() throws Exception {
LockingService service1 = newLockingService(0);
LockingService service2 = newLockingService(1);
assertTrue(service1.tryLock("lock1", "lock2"));
assertTrue(service2.tryLock("lock3", "lock4"));
}
@Test
public void shouldNotAcquireSameLockFromMultipleThreadOnTheSameClusterNode() throws Exception {
LockingService service1 = newLockingService(0);
assertTrue(service1.tryLock("lock1"));
CompletableFuture.runAsync(() -> assertLock(service1, false, "lock1")).get();
assertTrue(service1.unlock("lock1"));
CompletableFuture.runAsync(() -> assertLock(service1, true, "lock1")).get();
}
@Test
public void shouldPropagateLockInformationWhenChangingMembersInCluster() throws Exception {
LockingService service1 = newLockingService(0);
// lock while we're the only member
assertTrue(service1.tryLock("lock1"));
LockingService service2 = newLockingService(1);
// check that a new member can't get the lock yet
assertFalse(service2.tryLock("lock1"));
// and neither another thread...
CompletableFuture.runAsync(() -> assertLock(service1, false, "lock1")).get();
// now unlock
assertTrue(service1.unlock("lock1"));
Thread.sleep(100);
// check that the new member can get the lock
assertTrue(service2.tryLock("lock1"));
// shutdown the second member (which should release the lock)
service2.shutdown();
Thread.sleep(100);
// and now check that another thread can get the lock
CompletableFuture.runAsync(() -> assertLock(service1, true, "lock1")).get();
}
@Test
public void shouldNotAcquireSameLockInClusterSimultaneously1() throws Exception {
LockingService service1 = newLockingService(0);
LockingService service2 = newLockingService(1);
assertTrue(service1.tryLock("lock1", "lock2"));
assertFalse(service2.tryLock("lock2", "lock3"));
assertTrue(service1.unlock("lock2"));
Thread.sleep(100);
assertTrue(service2.tryLock("lock2", "lock3"));
assertFalse(service1.tryLock("lock3"));
assertTrue(service2.unlock("lock2", "lock3"));
}
@Test
public void shouldNotAcquireSameLockInClusterSimultaneously2() throws Exception {
LockingService service1 = newLockingService(0);
LockingService service2 = newLockingService(1);
LockingService service3 = newLockingService(2);
LockingService service4 = newLockingService(3);
assertTrue(service1.tryLock("lock1", "lock2"));
assertFalse(service2.tryLock("lock2", "lock3"));
assertFalse(service3.tryLock("lock4", "lock1"));
assertTrue(service4.tryLock("lock5", "lock6"));
assertFalse(service1.tryLock("lock1", "lock5"));
assertFalse(service1.tryLock("lock6", "lock1"));
assertTrue(service1.unlock("lock1", "lock2"));
assertTrue(service4.unlock("lock5", "lock6"));
Thread.sleep(100);
assertTrue(service2.tryLock("lock2", "lock3"));
assertTrue(service3.tryLock("lock4", "lock1"));
assertFalse(service1.tryLock("lock1", "lock5"));
assertFalse(service1.tryLock("lock6", "lock1"));
// now shut down service 2 and make sure the rest of the locks are unchanged..
service2.shutdown();
Thread.sleep(100);
assertFalse(service1.tryLock("lock1", "lock5"));
assertFalse(service4.tryLock("lock4"));
}
protected JGroupsLockingService newLockingService(int clusteredServiceIdx) {
ClusteringService service = clusteringServices.get(clusteredServiceIdx);
JGroupsLockingService lockingService = new JGroupsLockingService(service.getChannel(), 100);
lockingServices.add(lockingService);
return lockingService;
}
@Override
protected LockingService newLockingService() {
return new JGroupsLockingService(clusteringServices.get(0).getChannel(), 100);
}
}