INT-3616: Zookeeper LockRegistry
JIRA: https://jira.spring.io/browse/INT-3616 Zookeeper implementation of `LockRegistry`, for example to support clustered aggregators (when using external message group stores). INT-3616: Polishing; PR Comments
This commit is contained in:
committed by
Artem Bilan
parent
ec5230abc7
commit
06ffc19b0b
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* 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.springframework.integration.zookeeper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apache.curator.framework.CuratorFramework;
|
||||
import org.apache.curator.framework.CuratorFrameworkFactory;
|
||||
import org.apache.curator.retry.BoundedExponentialBackoffRetry;
|
||||
import org.apache.curator.test.TestingServer;
|
||||
import org.apache.curator.utils.CloseableUtils;
|
||||
import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
|
||||
/**
|
||||
* @author Marius Bogoevici
|
||||
* @author Gary Russell
|
||||
* @since 4.2
|
||||
*
|
||||
*/
|
||||
public class ZookeeperTestSupport {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(ZookeeperTestSupport.class);
|
||||
|
||||
protected final Log log = LogFactory.getLog(this.getClass());
|
||||
|
||||
protected static TestingServer testingServer;
|
||||
|
||||
protected CuratorFramework client;
|
||||
|
||||
@BeforeClass
|
||||
public static void setUpClass() throws Exception {
|
||||
testingServer = new TestingServer(true);
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDownClass() throws Exception {
|
||||
try {
|
||||
testingServer.stop();
|
||||
}
|
||||
catch (IOException e) {
|
||||
logger.warn("Exception thrown while shutting down ZooKeeper: ", e);
|
||||
}
|
||||
testingServer.getTempDirectory().delete();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception{
|
||||
client = createNewClient();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
CloseableUtils.closeQuietly(this.client);
|
||||
}
|
||||
|
||||
protected CuratorFramework createNewClient() throws InterruptedException {
|
||||
CuratorFramework client = CuratorFrameworkFactory.newClient(testingServer.getConnectString(),
|
||||
new BoundedExponentialBackoffRetry(100, 1000, 3));
|
||||
client.start();
|
||||
client.blockUntilConnected(10000, TimeUnit.SECONDS);
|
||||
return client;
|
||||
}
|
||||
|
||||
protected void closeClient(CuratorFramework client) {
|
||||
try {
|
||||
CloseableUtils.closeQuietly(client);
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.warn("Exception thrown while closing client: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,313 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* 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.springframework.integration.zookeeper.lock;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotSame;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.integration.test.util.TestUtils;
|
||||
import org.springframework.integration.zookeeper.ZookeeperTestSupport;
|
||||
import org.springframework.integration.zookeeper.lock.ZookeeperLockRegistry.KeyToPathStrategy;
|
||||
import org.springframework.messaging.MessagingException;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @author Gary Russell
|
||||
* @since 4.2
|
||||
*
|
||||
*/
|
||||
public class ZkLockRegistryTests extends ZookeeperTestSupport {
|
||||
|
||||
@Test
|
||||
public void testLock() throws Exception {
|
||||
ZookeeperLockRegistry registry = new ZookeeperLockRegistry(this.client);
|
||||
for (int i = 0; i < 10; i++) {
|
||||
Lock lock = registry.obtain("foo");
|
||||
lock.lock();
|
||||
try {
|
||||
assertEquals(1, TestUtils.getPropertyValue(registry, "locks", Map.class).size());
|
||||
}
|
||||
finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
Thread.sleep(10);
|
||||
registry.expireUnusedOlderThan(0);
|
||||
assertEquals(0, TestUtils.getPropertyValue(registry, "locks", Map.class).size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLockInterruptibly() throws Exception {
|
||||
ZookeeperLockRegistry registry = new ZookeeperLockRegistry(this.client);
|
||||
for (int i = 0; i < 10; i++) {
|
||||
Lock lock = registry.obtain("foo");
|
||||
lock.lockInterruptibly();
|
||||
try {
|
||||
assertEquals(1, TestUtils.getPropertyValue(registry, "locks", Map.class).size());
|
||||
}
|
||||
finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReentrantLock() throws Exception {
|
||||
ZookeeperLockRegistry registry = new ZookeeperLockRegistry(this.client);
|
||||
for (int i = 0; i < 10; i++) {
|
||||
Lock lock1 = registry.obtain("foo");
|
||||
lock1.lock();
|
||||
try {
|
||||
Lock lock2 = registry.obtain("foo");
|
||||
assertSame(lock1, lock2);
|
||||
lock2.lock();
|
||||
lock2.unlock();
|
||||
}
|
||||
finally {
|
||||
lock1.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReentrantLockInterruptibly() throws Exception {
|
||||
ZookeeperLockRegistry registry = new ZookeeperLockRegistry(this.client);
|
||||
for (int i = 0; i < 10; i++) {
|
||||
Lock lock1 = registry.obtain("foo");
|
||||
lock1.lockInterruptibly();
|
||||
try {
|
||||
Lock lock2 = registry.obtain("foo");
|
||||
assertSame(lock1, lock2);
|
||||
lock2.lockInterruptibly();
|
||||
lock2.unlock();
|
||||
}
|
||||
finally {
|
||||
lock1.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTwoLocks() throws Exception {
|
||||
ZookeeperLockRegistry registry = new ZookeeperLockRegistry(this.client);
|
||||
for (int i = 0; i < 10; i++) {
|
||||
Lock lock1 = registry.obtain("foo");
|
||||
lock1.lockInterruptibly();
|
||||
try {
|
||||
Lock lock2 = registry.obtain("bar");
|
||||
assertNotSame(lock1, lock2);
|
||||
lock2.lockInterruptibly();
|
||||
lock2.unlock();
|
||||
}
|
||||
finally {
|
||||
lock1.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTwoThreadsSecondFailsToGetLock() throws Exception {
|
||||
final ZookeeperLockRegistry registry = new ZookeeperLockRegistry(this.client);
|
||||
final Lock lock1 = registry.obtain("foo");
|
||||
lock1.lockInterruptibly();
|
||||
final AtomicBoolean locked = new AtomicBoolean();
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
Future<Object> result = Executors.newSingleThreadExecutor().submit(new Callable<Object>() {
|
||||
|
||||
@Override
|
||||
public Object call() throws Exception {
|
||||
Lock lock2 = registry.obtain("foo");
|
||||
locked.set(lock2.tryLock(200, TimeUnit.MILLISECONDS));
|
||||
latch.countDown();
|
||||
try {
|
||||
lock2.unlock();
|
||||
}
|
||||
catch (MessagingException e) {
|
||||
return e.getCause();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
assertTrue(latch.await(10, TimeUnit.SECONDS));
|
||||
assertFalse(locked.get());
|
||||
lock1.unlock();
|
||||
Object ise = result.get(10, TimeUnit.SECONDS);
|
||||
assertThat(ise, instanceOf(IllegalMonitorStateException.class));
|
||||
assertThat(((Exception) ise).getMessage(), containsString("You do not own"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTwoThreads() throws Exception {
|
||||
final ZookeeperLockRegistry registry = new ZookeeperLockRegistry(this.client);
|
||||
final Lock lock1 = registry.obtain("foo");
|
||||
final AtomicBoolean locked = new AtomicBoolean();
|
||||
final CountDownLatch latch1 = new CountDownLatch(1);
|
||||
final CountDownLatch latch2 = new CountDownLatch(1);
|
||||
final CountDownLatch latch3 = new CountDownLatch(1);
|
||||
lock1.lockInterruptibly();
|
||||
Executors.newSingleThreadExecutor().execute(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Lock lock2 = registry.obtain("foo");
|
||||
try {
|
||||
latch1.countDown();
|
||||
lock2.lockInterruptibly();
|
||||
latch2.await(10, TimeUnit.SECONDS);
|
||||
locked.set(true);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
finally {
|
||||
lock2.unlock();
|
||||
latch3.countDown();
|
||||
}
|
||||
}
|
||||
});
|
||||
assertTrue(latch1.await(10, TimeUnit.SECONDS));
|
||||
assertFalse(locked.get());
|
||||
lock1.unlock();
|
||||
latch2.countDown();
|
||||
assertTrue(latch3.await(10, TimeUnit.SECONDS));
|
||||
assertTrue(locked.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTwoThreadsDifferentRegistries() throws Exception {
|
||||
final ZookeeperLockRegistry registry1 = new ZookeeperLockRegistry(this.client);
|
||||
final ZookeeperLockRegistry registry2 = new ZookeeperLockRegistry(this.client);
|
||||
final Lock lock1 = registry1.obtain("foo");
|
||||
final AtomicBoolean locked = new AtomicBoolean();
|
||||
final CountDownLatch latch1 = new CountDownLatch(1);
|
||||
final CountDownLatch latch2 = new CountDownLatch(1);
|
||||
final CountDownLatch latch3 = new CountDownLatch(1);
|
||||
lock1.lockInterruptibly();
|
||||
Executors.newSingleThreadExecutor().execute(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Lock lock2 = registry2.obtain("foo");
|
||||
try {
|
||||
latch1.countDown();
|
||||
lock2.lockInterruptibly();
|
||||
latch2.await(10, TimeUnit.SECONDS);
|
||||
locked.set(true);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
finally {
|
||||
lock2.unlock();
|
||||
latch3.countDown();
|
||||
}
|
||||
}
|
||||
});
|
||||
assertTrue(latch1.await(10, TimeUnit.SECONDS));
|
||||
assertFalse(locked.get());
|
||||
lock1.unlock();
|
||||
latch2.countDown();
|
||||
assertTrue(latch3.await(10, TimeUnit.SECONDS));
|
||||
assertTrue(locked.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTwoThreadsWrongOneUnlocks() throws Exception {
|
||||
final ZookeeperLockRegistry registry = new ZookeeperLockRegistry(this.client);
|
||||
final Lock lock = registry.obtain("foo");
|
||||
lock.lockInterruptibly();
|
||||
final AtomicBoolean locked = new AtomicBoolean();
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
Future<Object> result = Executors.newSingleThreadExecutor().submit(new Callable<Object>() {
|
||||
|
||||
@Override
|
||||
public Object call() throws Exception {
|
||||
try {
|
||||
lock.unlock();
|
||||
}
|
||||
catch (Exception e) {
|
||||
latch.countDown();
|
||||
return e.getCause();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
assertTrue(latch.await(10, TimeUnit.SECONDS));
|
||||
assertFalse(locked.get());
|
||||
lock.unlock();
|
||||
Object imse = result.get(10, TimeUnit.SECONDS);
|
||||
assertThat(imse, instanceOf(IllegalMonitorStateException.class));
|
||||
assertThat(((Exception) imse).getMessage(), containsString("You do not own"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLockWithBoundedStrategy() throws Exception {
|
||||
ZookeeperLockRegistry registry = new ZookeeperLockRegistry(this.client, new KeyToPathStrategy() {
|
||||
|
||||
@Override
|
||||
public String pathFor(String key) {
|
||||
return "/SpringIntegration-LockRegistry/singleLock";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean bounded() {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
for (int i = 0; i < 10; i++) {
|
||||
Lock lock = registry.obtain("foo");
|
||||
lock.lock();
|
||||
try {
|
||||
assertEquals(1, TestUtils.getPropertyValue(registry, "locks", Map.class).size());
|
||||
}
|
||||
finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
Thread.sleep(10);
|
||||
try {
|
||||
registry.expireUnusedOlderThan(0);
|
||||
fail("expected exception");
|
||||
}
|
||||
catch (IllegalStateException e) {
|
||||
assertThat(e.getMessage(), containsString("expiry is not supported"));
|
||||
}
|
||||
assertEquals(1, TestUtils.getPropertyValue(registry, "locks", Map.class).size());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -25,7 +25,6 @@ import static org.junit.Assert.fail;
|
||||
import static org.springframework.integration.test.matcher.EqualsResultMatcher.equalsResult;
|
||||
import static org.springframework.integration.test.matcher.EventuallyMatcher.eventually;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
@@ -34,71 +33,44 @@ import java.util.Map;
|
||||
import java.util.concurrent.CyclicBarrier;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apache.curator.framework.CuratorFramework;
|
||||
import org.apache.curator.framework.CuratorFrameworkFactory;
|
||||
import org.apache.curator.retry.BoundedExponentialBackoffRetry;
|
||||
import org.apache.curator.test.TestingServer;
|
||||
import org.apache.curator.utils.CloseableUtils;
|
||||
import org.hamcrest.collection.IsIterableContainingInOrder;
|
||||
import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import org.springframework.beans.DirectFieldAccessor;
|
||||
import org.springframework.integration.metadata.MetadataStoreListenerAdapter;
|
||||
import org.springframework.integration.metadata.MetadataStoreListener;
|
||||
import org.springframework.integration.metadata.MetadataStoreListenerAdapter;
|
||||
import org.springframework.integration.support.utils.IntegrationUtils;
|
||||
import org.springframework.integration.test.matcher.EqualsResultMatcher.Evaluator;
|
||||
import org.springframework.integration.zookeeper.ZookeeperTestSupport;
|
||||
|
||||
/**
|
||||
* @author Marius Bogoevici
|
||||
* @since 4.2
|
||||
*/
|
||||
public class ZookeeperMetadataStoreTests {
|
||||
|
||||
private static final Log log = LogFactory.getLog(ZookeeperMetadataStore.class);
|
||||
|
||||
private static TestingServer testingServer;
|
||||
|
||||
private CuratorFramework client;
|
||||
public class ZookeeperMetadataStoreTests extends ZookeeperTestSupport {
|
||||
|
||||
private ZookeeperMetadataStore metadataStore;
|
||||
|
||||
@BeforeClass
|
||||
public static void setUpClass() throws Exception {
|
||||
testingServer = new TestingServer(true);
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDownClass() throws Exception {
|
||||
try {
|
||||
testingServer.stop();
|
||||
}
|
||||
catch (IOException e) {
|
||||
log.warn("Exception thrown while shutting down ZooKeeper: ", e);
|
||||
}
|
||||
testingServer.getTempDirectory().delete();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Before
|
||||
public void setUp() throws Exception{
|
||||
client = createNewClient();
|
||||
metadataStore = new ZookeeperMetadataStore(client);
|
||||
metadataStore.start();
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
this.metadataStore = new ZookeeperMetadataStore(client);
|
||||
this.metadataStore.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
this.metadataStore.stop();
|
||||
this.client.delete().deletingChildrenIfNeeded().forPath(this.metadataStore.getRoot());
|
||||
CloseableUtils.closeQuietly(this.client);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testGetNonExistingKeyValue() {
|
||||
String retrievedValue = metadataStore.get("does-not-exist");
|
||||
@@ -413,12 +385,4 @@ public class ZookeeperMetadataStoreTests {
|
||||
}
|
||||
}
|
||||
|
||||
private CuratorFramework createNewClient() throws InterruptedException {
|
||||
CuratorFramework client = CuratorFrameworkFactory.newClient(testingServer.getConnectString(),
|
||||
new BoundedExponentialBackoffRetry(100, 1000, 3));
|
||||
client.start();
|
||||
client.blockUntilConnected(10000, TimeUnit.SECONDS);
|
||||
return client;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user